Skip to main content

Why is useEffect not guaranteed to run right after commit?

1. What the commit phase does

In React, every update cycle is split into two big parts:

PhaseWhat it doesCan it be interrupted?
Render PhaseReact computes what needs to change (a new Virtual DOM)Yes
Commit PhaseReact applies the changes to the real DOMNo

After the commit phase, the DOM is already updated, the screen is repainted, and React is ready to call effects (useEffect, useLayoutEffect, componentDidMount, and so on).

But, importantly, not all effects have the same priority.


2. Different types of effects

React distinguishes two types of side effects:

TypeHookWhen it runs
Synchronous (layout)useLayoutEffectright after commit, before the screen is painted
PassiveuseEffectasynchronously, after painting and the browser's paint tick

3. Why useEffect() is asynchronous

After commit, React:

  1. Updates the DOM.
  2. Tells the browser: "ready, the screen can be repainted".
  3. Waits for the browser to finish painting.
  4. Only then calls all the "passive" effects (useEffect).

This is needed so that:

  • the UI becomes visible as fast as possible;
  • effects (network requests, timers, subscriptions, and so on) do not slow down the repaint;
  • the browser stays responsive (scroll, input, animation are not blocked).

Example (the difference between useLayoutEffect and useEffect)

javascript
useLayoutEffect(() => { console.log('useLayoutEffect'); // synchronous, after the DOM update }); useEffect(() => { console.log('useEffect'); // asynchronous, after paint });

Console log:

javascript
render commit useLayoutEffect ← right after commit PAINT (browser) useEffect ← after painting

4. Why this matters in React 18 and concurrent rendering

In React 18, rendering became interruptible and priority-based (Concurrent Rendering). Because of this, React can:

  • delay the useEffect() call if there are more urgent tasks (for example, the user clicked something);
  • cancel effects if the render was aborted before commit;
  • merge several renders into one commit and call the effects only once after it.

So React guarantees only one thing: useEffect() will be called after the DOM has already been updated and the browser has shown the changes, but not necessarily instantly - it could be on the next event loop tick.


5. What if you need an effect "right after commit"?

Then you need to use useLayoutEffect():

javascript
useLayoutEffect(() => { console.log('Synchronously after commit'); });

useLayoutEffect:

  • runs synchronously, right after React updates the DOM;
  • blocks painting until the callback finishes;
  • is ideal for measurements (for example, getBoundingClientRect).

But using useLayoutEffect without a real need is risky, it can delay the UI from rendering. For most side effects (fetch, subscriptions, timers), useEffect is preferred.


6. Visually

javascript
Render phase Commit phase (DOM updated) useLayoutEffect (right away) Browser painted useEffect (asynchronously)

7. Summary

React does not call useEffect right after commit, because useEffect is a passive effect that runs asynchronously after the browser has finished painting.

This guarantees that:

  • the UI renders as fast as possible,
  • effects do not block the interface,
  • concurrent rendering stays responsive.

HookWhen it runsUse it for
useLayoutEffectright after commit (synchronously)DOM measurements, layout synchronization
useEffectafter paint (asynchronously)side effects, requests, subscriptions

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.