Skip to content

Instantly share code, notes, and snippets.

@tomhicks
Created January 11, 2021 11:41
Show Gist options
  • Save tomhicks/95d455186fadda2f7d99e6163aa9d360 to your computer and use it in GitHub Desktop.
Save tomhicks/95d455186fadda2f7d99e6163aa9d360 to your computer and use it in GitHub Desktop.
React Hook for queueing and processing async tasks sequentially
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
@majames
Copy link

majames commented Jun 10, 2024

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

https://react.dev/learn/queueing-a-series-of-state-updates

@sladkoff
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment