useLayoutEffect vs useEffect: When Paint Timing Actually Matters

useEffect fires after the browser paints. useLayoutEffect fires before it. For most things, the difference is invisible — but for DOM measurement and position-dependent UI, it is the difference between a flicker and a clean render.

June 7, 20263 min read

useLayoutEffect has the same API as useEffect. The difference is when it runs relative to the browser's paint cycle.

useEffect runs asynchronously, after the browser has painted the updated DOM to the screen. The user sees the new UI, then the effect executes.

useLayoutEffect runs synchronously, after React has committed DOM changes but before the browser paints. The effect executes in the gap between the DOM existing in its new state and the user seeing it.

For most use cases — data fetching, subscriptions, analytics — this timing difference is invisible. The effect runs a fraction of a millisecond after paint and nothing perceivable changes. But for one specific category of work, the difference is visible: reading or writing the DOM in a way that affects layout.

The Flicker Problem

Consider a component that measures its own width and displays it:

TSX
const boxRef = useRef(null); const [boxWidth, setBoxWidth] = useState(0); useEffect(() => { setBoxWidth(boxRef.current.getBoundingClientRect().width); }, []); return ( <div ref={boxRef}> Width: {boxWidth}px </div> );

With useEffect, the sequence is:

  1. React renders the component with boxWidth = 0
  2. Browser paints — user briefly sees "Width: 0px"
  3. Effect runs, measures the real width, calls setBoxWidth
  4. React re-renders with the correct value
  5. Browser paints again — user sees "Width: 154px"

That step 2 is the flicker. On fast machines it is invisible. On slower hardware, complex layouts, or first load, it shows as a brief flash of incorrect content before the correct value appears.

useLayoutEffect eliminates step 2 entirely. The measurement happens before the first paint, so the user only ever sees the correct value.

TSX
useLayoutEffect(() => { setBoxWidth(boxRef.current.getBoundingClientRect().width); }, []);

The sequence becomes: render → measure → re-render → paint once with the correct value.

When to Use useLayoutEffect

useLayoutEffect belongs in a narrow set of situations:

  • Reading DOM geometry: getBoundingClientRect, offsetWidth, scrollHeight — measurements that only make sense after the DOM exists in its final state
  • Synchronous DOM writes: repositioning a tooltip, adjusting a popover, correcting scroll position before the user sees it
  • Avoiding layout flicker: any case where computing a value from the DOM and then updating state would cause a visible double-paint with useEffect

Outside these cases, use useEffect. Always.

Why Not Always Use useLayoutEffect

useLayoutEffect is synchronous and blocks the browser from painting until it completes. A slow or expensive useLayoutEffect delays when the user sees anything on screen — degrading perceived performance in a way that useEffect would not.

The React team is explicit about this: useLayoutEffect should be reserved for cases where synchronous execution is genuinely necessary. For everything else — fetching data, logging, subscribing to external stores — the asynchronous behavior of useEffect is correct and preferred.

Default to useEffect. Reach for useLayoutEffect only when you need to read or write the DOM before paint.


useInsertionEffect: A Note for Library Authors

React also provides useInsertionEffect, which runs even earlier than useLayoutEffect — before any DOM mutations are made during rendering.

Its sole purpose is injecting CSS styles into the document during render, ensuring they are applied before any layout calculations happen. This is specifically for CSS-in-JS library authors (styled-components, Emotion, and similar) who need to insert <style> tags before components read layout.

TSX
useInsertionEffect(() => { const style = document.createElement('style'); style.textContent = `.my-class { color: red; }`; document.head.appendChild(style); return () => style.remove(); }, []);

The React team explicitly scopes this hook to CSS-in-JS library authors. If you are writing application code, you will never need it. useEffect and useLayoutEffect cover every legitimate application use case.

Practice what you just read.

useEffect or useLayoutEffect?
1 exercise