useLayoutEffect — measure before paint

[check: …]
📖 guide (.md) ← react deep dive
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth. ↗ Sibling of frontend/react: useEffect — same signature, different timing. useEffect fires AFTER paint (async); useLayoutEffect fires BEFORE paint (sync).

useEffect vs useLayoutEffect: same signature, different timing

Both hooks share the signature useXxxEffect(setup, deps). The ONLY difference is when the setup function runs relative to the browser paint. useEffect is asynchronous — the browser paints first, THEN the effect runs, so the user may briefly see a stale DOM. useLayoutEffect is synchronous — it runs after DOM mutation but BEFORE paint, so any setState inside it produces a re-render that the user sees as one flicker-free frame.

phasewhat happenswhich hook runs
1. render React calls your component, computes the virtual DOM —
2. commit React mutates the real DOM to match the virtual DOM —
3. layout useLayoutEffect setup runs synchronously — can read layout (offsetWidth, getBoundingClientRect), can setState useLayoutEffect
4. paint browser paints pixels to the screen — user sees the frame —
5. passive useEffect setup runs asynchronously — non-blocking, the user already saw the painted DOM useEffect

1 · the measure-before-paint hook (edit me)

2 · Babel compiles JSX → element tree

<LayoutEffectDemo/> becomes React.createElement(LayoutEffectDemo). After commit, React fires useLayoutEffect synchronously — it reads boxRef.current.offsetWidth, calls setWidth, and React re-renders BEFORE the browser paints.

// (hit "compile & render" to see Babel's output)

3 · live React (measure-before-paint, proven)

Click Grow +50 / Shrink −50: the Measured: W×Hpx line updates in the SAME frame as the box resize. The gold-check asserts the sync pipeline: initial 100×100 → Grow 150×150 → Grow 200×200 → Shrink 150×150. If we used useEffect instead, you would see one painted frame with a stale measurement before it corrected.

dimensions: — · gold: 100×100 → Grow → 150×150 → Grow → 200×200 → Shrink → 150×150

intent → hook

intenthookwhy
data fetching, subscriptions, logging useEffect non-blocking; user shouldn't wait on a network round-trip before paint
measure element size/position before showing it useLayoutEffect reads offsetWidth/getBoundingClientRect before paint — no flicker
scroll to a position right after content mounts useLayoutEffect sets scrollTop before the user sees the wrong scroll offset
position a tooltip/popover relative to an anchor useLayoutEffect measures the anchor box, computes coords, paints once — no jump
animate from 0 to a measured value useLayoutEffect + useState read the target size, then animate; otherwise the first frame is wrong
SSR (no real DOM) useEffect or useInsertionEffect useLayoutEffect warns on the server — wrap in typeof window !== 'undefined' or use a useIsomorphicLayoutEffect shim