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