-
-
Save tomhicks/95d455186fadda2f7d99e6163aa9d360 to your computer and use it in GitHub Desktop.
function useTaskQueue(params: { | |
shouldProcess: boolean | |
}): { | |
tasks: ReadonlyArray<Task> | |
isProcessing: boolean | |
addTask: (task: Task) => void | |
} { | |
const [queue, setQueue] = React.useState<{ | |
isProcessing: boolean | |
tasks: Array<Task> | |
}>({isProcessing: false, tasks: []}) | |
React.useEffect(() => { | |
if (!params.shouldProcess) return | |
if (queue.tasks.length === 0) return | |
if (queue.isProcessing) return | |
const task = queue.tasks[0] | |
setQueue((prev) => ({ | |
isProcessing: true, | |
tasks: prev.tasks.slice(1), | |
})) | |
Promise.resolve(task()).finally(() => { | |
setQueue((prev) => ({ | |
isProcessing: false, | |
tasks: prev.tasks, | |
})) | |
}) | |
}, [queue, params.shouldProcess]) | |
return { | |
tasks: queue.tasks, | |
isProcessing: queue.isProcessing, | |
addTask: React.useCallback((task) => { | |
setQueue((prev) => ({ | |
isProcessing: prev.isProcessing, | |
tasks: [...prev.tasks, task], | |
})) | |
}, []), | |
} | |
} | |
type Task = () => Promise<void> | void |
Thank you! This was the architecture I was looking for to implement queue processing in the background.
Using this to make image upload to AWS in the non-block blocking way for the end user. Thanks for sharing this!
Thanks ~
I started getting `Maximum update depth exceeded' errors since upgrading to React 18.
After some debugging and research, I could pin the cause on the new automatic batching in React 18.
In this hook, there are two consecutive setQueue(...)
calls inside the useEffect
which must update the internal state in the correct order. Otherwise there's the possibility for inifinite re-renders which cause the above error. This only happens if the enqueued task()
is very fast.
This can be fixed by wrapping the first setQueue
call in flushSync
to prevent batching:
flushSync(() => {
setQueue((prev) => ({
isProcessing: true,
tasks: prev.tasks.slice(1),
}))
})
As the use of flushSync
is discouraged, I'd also be interested in a different solution.
PS. Thanks for the original hook!
In this hook, there are two consecutive setQueue(...) calls inside the useEffect which must update the internal state in the correct order. Otherwise there's the possibility for inifinite re-renders which cause the above error. This only happens if the enqueued task() is very fast.
@sladkoff not sure this is correct? both setQueue()
calls should be queued and, therefore, the task should be removed from the queue even if it runs really quickly
Hi @majames, I just double-checked this in my codebase. I noticed that I can remove the flushSync(...)
now and the original problem is not reproducible.
However, I was able to reproduce the problem on the code revision from a year ago... It could indeed be fixed by applying the flushSync(...)
wrapper... It doesn't make a lot of sense to me. I'm assuming it has something to do with the context of my code and maybe some dependency constellation 🤷
You're probably right that this is not the correct way to do this.
Thanks a lot!!