QuestionsReact

useLayoutEffect vs useEffect

Effects / TimingMediumReact

When would you reach for useLayoutEffect instead of useEffect, and what's the cost of getting it wrong?

What it tests

Understanding the browser paint cycle and where each effect fires relative to it.

Approach & answer

Both run after render, but at different points relative to paint. useEffect fires asynchronously *after* the browser has painted — so if your effect mutates the DOM in a way the user can see (measuring an element then repositioning a tooltip, syncing scroll position), the user briefly sees the un-adjusted frame, i.e. a flicker. useLayoutEffect fires synchronously *after DOM mutations but before paint*, so you can read layout (getBoundingClientRect, offsetHeight) and write derived styles in the same frame, and the user never sees the intermediate state. The cost: useLayoutEffect blocks painting, so heavy work there freezes the UI — use it only for reads/writes that must happen before paint, and keep it cheap. Default to useEffect; escalate to useLayoutEffect only to kill a visible flicker caused by measuring-then-mutating layout. Note it also warns during SSR because there's no layout phase on the server — guard with a client check or use the useEffect fallback for isomorphic components.

Use this technique when

You measure the DOM then mutate it and see a flicker → useLayoutEffect (before paint). Otherwise useEffect.

Code

function Tooltip({ targetRef }) {
  const tipRef = React.useRef(null);
  const [pos, setPos] = React.useState({ top: 0, left: 0 });
  // Runs before paint: measure target, position tooltip in the same frame.
  React.useLayoutEffect(() => {
    const t = targetRef.current.getBoundingClientRect();
    const h = tipRef.current.offsetHeight;
    setPos({ top: t.top - h, left: t.left }); // no visible flicker
  }, [targetRef]);
  return <div ref={tipRef} style={{ position: 'fixed', ...pos }}>Hint</div>;
}

References