Back to React
React
medium
mid

What is the difference between useEffect and useLayoutEffect, and when do you use each?

`useEffect` runs after the browser paints — async, doesn't block visual updates. `useLayoutEffect` runs synchronously after the DOM mutates but BEFORE paint — use it when you need to measure layout and mutate the DOM before the user sees anything. Default to `useEffect`; reach for `useLayoutEffect` only for measure-and-adjust patterns (tooltips, animations from a measured position).

5 min read·~10 min to think through

Same API, different scheduling.

ts
render → commit (DOM updates) → useLayoutEffect (sync, blocks paint) → paint → useEffect (async)

When to use each

useEffect (the default).

  • Side effects that don't need to block rendering: fetches, subscriptions, logging, timers.
  • Runs after paint, so the user sees the new UI as fast as possible.
  • Cleanup runs before the next effect or on unmount.

useLayoutEffect.

  • You need to read layout (height, width, scroll position) and mutate the DOM before the user sees a flash of the wrong state.
  • Examples:
  • Position a tooltip relative to a trigger button.
  • Measure a list item's height and use it for parent layout.
  • Synchronously focus an element after mount where useEffect would cause a visible flicker.
  • Custom scroll restoration where you need to set scrollTop before paint.
tsx
function Tooltip({ targetRef, children }) {
  const ref = useRef<HTMLDivElement>(null);
  const [pos, setPos] = useState({ top: 0, left: 0 });

  useLayoutEffect(() => {
    const r = targetRef.current!.getBoundingClientRect();
    setPos({ top: r.bottom + 4, left: r.left });
  }, [targetRef]);

  return <div ref={ref} style={pos}>{children}</div>;
}

If you used useEffect here, the user would see the tooltip briefly at the wrong position (0,0) before it jumped to the right place.

The trade-off

useLayoutEffect runs synchronously and blocks the browser from painting. Heavy work inside it makes interactions feel laggy. Use it sparingly — only when not using it produces a visible artifact.

The general rule: start with useEffect. If you see a flicker, reach for useLayoutEffect.

SSR

useLayoutEffect doesn't run on the server. React emits a warning when it runs in SSR contexts ("useLayoutEffect does nothing on the server"). For libraries that need to support both:

ts
const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;

(useInsertionEffect runs even earlier — used by CSS-in-JS libraries to inject styles before any layout effect reads them. Don't reach for it unless you're building a CSS-in-JS lib.)

Common pitfalls

  1. Heavy work in useLayoutEffect. Blocks paint. If you're doing more than DOM measurement + setState, it probably belongs in useEffect.
  1. Fetching in useLayoutEffect. Never. Fetches are async; the layout effect can't await them anyway, and the synchronous part you do execute blocks paint.
  1. Cascading layout effects. Each layout effect that triggers a setState causes another synchronous render. Two or three nested can compound into noticeable jank.
  1. Reading layout in render. elem.getBoundingClientRect() during render returns stale values (DOM not committed yet). Read in an effect, set state, re-render with the measurement.

Order of effects

Within a tree:

  • All useLayoutEffects in children → then in parents (bubble up).
  • Then the same order for useEffects, but async after paint.

If a child measures and a parent layout effect depends on it: the child runs first; the parent's read-after sees the updated DOM (within the same commit) and child setState's render is part of the same paint.

Senior framing

The interviewer wants:

  1. Knowing the scheduling difference — paint boundary.
  2. A specific use case for useLayoutEffect (tooltip, measure-and-adjust).
  3. Default to useEffect discipline.
  4. SSR awareness — useLayoutEffect doesn't run on the server.
  5. Cost awareness — useLayoutEffect blocks paint.

The "useLayoutEffect runs before paint" answer is mid. Naming the measure-and-adjust pattern + cost + SSR caveat is senior.

Follow-up questions

  • What is useInsertionEffect and why does it exist?
  • Why doesn't useLayoutEffect run on the server?
  • When does useEffect cause a visible flicker that useLayoutEffect fixes?
  • How do nested layout effects compose?

Common mistakes

  • Reaching for useLayoutEffect everywhere 'to be safe' — kills perf.
  • Doing async work in useLayoutEffect.
  • Reading layout in render instead of in an effect.
  • Forgetting cleanup — both hooks need it for subscriptions/timers.

Performance considerations

  • useLayoutEffect blocks paint — keep it minimal.
  • useEffect schedules work after paint — safer default.

Edge cases

  • SSR: useLayoutEffect warns; use the isomorphic alternative.
  • Strict Mode double-runs effects (incl. layout) — keep them idempotent.
  • Nested layout effects can produce visible mid-paint state if they trigger more renders.

Real-world examples

  • Popper.js / Floating UI tooltips — use layout effects to position before paint.
  • Headless UI's Listbox / Menu — measure-and-adjust on open.

Related questions