Does the useEffect
hook run in the same browser render cycle, in React 18, after the component has been rendered or it is possible that the hook will be delayed and run later? In other words, does React guarantee that render and the corresponding useEffect
hook are executed atomically?
My specific case, although I do not think it matters: my component renders an editor and I need to focus the editor once it is rendered if the client code specified the autofocus option. I imagine that if useEffect
is run in a subsequent browser render cycle, it is possible to set focus to another component after the editor has been rendered but before useEffect
is executed, which would effectively steal the focus from that other component.
2
Answers
As per the docs, React
useEffect
Hook is executed after the Component render.But it is not executed immediately after, if that is what you mean by "atomically". In particular, React renders first all the child Components, and also executes their own
useEffect
Hooks first, from descendants to ancestors (see What is the correct order of execution of useEffect in React parent and child components?)Indeed, since only 1 Element can have browser focus, the last executed
.focus()
expression wins.As described above, all child Components will have their
useEffect
Hook executed first, hence they may already try to apply focus. Hence you may be stealing focus from one of your children, which may not be problematic depending on your exact use case.Similarly, another Component may steal the focus of your Editor, e.g. if it is a next sibling or parent Component.
BTW, here "steal" does not mean that focus will visually jump from one Element to another: all
useEffect
Hooks will be executed (in above described order) before next React render attempt:So only the last executed
.focus()
will effectively visually appear to the visitor.There will be things that happen in between, such as updating the dom and the browser painting. However, it is not possible that it will be delayed until after another render. If you’re using concurrent features such as startTransition then it is possible that the current render will be aborted, but in that case the effect will not run at all.
In short: if your effect code runs, you can be guaranteed that it was created by the most recent render and is being run after that render.
P.S, i did a test to see if i could force a render to happen synchronously, before some effects have run, using flushSync. Turns out react does not allow using
flushSync
during a lifecycle hook, so this can not break what i described above.Sandbox