Topic 7.8
useLayoutEffect and useInsertionEffect
In one line
useLayoutEffect runs after React updates the DOM but before the browser paints, so you can measure elements and fix the layout without a visible flicker, at the cost of blocking the paint; useInsertionEffect runs even earlier and exists for CSS-in-JS libraries to insert styles.
Think of it like this
Hanging a picture. You hold it against the wall, check with a level that it's straight, adjust, and only then step back so guests can look. You don't want visitors to watch it go up crooked and then move. useLayoutEffect is checking with the level before anyone looks: measure and adjust before the browser shows the frame.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Layout
- The browser working out the size and position of every element.
- Measure
- Read an element's size or position, e.g. with
getBoundingClientRect(). - Paint
- The browser drawing the frame on the screen.
- useLayoutEffect
- An effect that runs after the DOM update but before the browser paints.
- Flicker
- A visible jump when the user briefly sees a wrong layout before it is fixed.
- Blocking paint
- Keeping the browser busy so it can't draw the next frame.
- useInsertionEffect
- An effect that runs before layout effects, for libraries that insert CSS.
- CSS-in-JS
- Libraries that write CSS from JavaScript code, such as styled-components or Emotion.
- SSR
- Server-side rendering: turning components into HTML on the server. No effects run there.
Step by step
01The three effect hooks run at different moments
During a commit, React runs insertion effects, then updates refs and runs layout effects, then lets the browser paint, then runs ordinary effects.
02Render with a first guess
The component can't know its size before it is in the DOM, so it renders with a guess: tooltip above the button.
const [placement, setPlacement] = useState("above");03Measure before paint and correct
In the layout effect the DOM exists, so you can measure. If the guess was wrong, set state: React re-renders and commits before the browser paints.
useLayoutEffect(() => {
const tipHeight = tipRef.current.getBoundingClientRect().height;
const spaceAbove = buttonRef.current.getBoundingClientRect().top;
if (tipHeight > spaceAbove) setPlacement("below");
}, []);04Keep it fast
Everything in a layout effect delays the frame. Read the few numbers you need, set state once, and do any slow work (logging, network) in a normal useEffect.
05Remember the server
On the server, layout effects don't run, so the HTML shows the first guess. If that guess is visibly wrong, render the component only on the client.
function ClientOnly({ children }) {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
return mounted ? children : null;
}Try it yourself
- 1
Give the tooltip room
In the tooltip example, change the body padding to
80px. Predict the logs. (The space above is now 80px, more than 40, so there's no second render and the tooltip stays above.) - 2
Swap to useEffect
Change
useLayoutEffecttouseEffectin the tooltip example. The logs are the same, but now the correction may happen after a paint. On a slow phone you can see the tooltip flash at the top first. That flash is what layout effects prevent. - 3
Make the measurement wrong
In the insertion example, change
useInsertionEffecttouseEffectinsideuseCss. Predict the widths now. (The layout effects run first, before the style exists, so they measure the unstyled span's width.)
Code & diagrams
First the component is rendered to an HTML string the way a server would: it renders, but **no** effect of any kind runs, and React 19 prints no warning. In the browser all three run, in this order.
Preview
Console
Nothing logged yet.Expected console output
render, h = 0
<div style="height:40px">box</div>
--- client
render, h = 0
insertion effect
layout effect
effectThe button sits 16px from the top of the page, and the tooltip needs 40px, so it doesn't fit above. The layout effect measures and switches to `below`, and React re-renders before the first paint: the user never sees the tooltip cut off at the top.
Preview
Console
Nothing logged yet.Expected console output
render, placement = above
layout effect: tip is 40px tall, 16px of space above
render, placement = belowEach `Badge` asks for its style; the first insertion adds the `<style>` tag (the `Set` prevents duplicates). Because all insertion effects run before any layout effect, both measurements see the 120px width from the injected CSS.
Preview
Console
Nothing logged yet.Expected console output
insertion effect: added <style> for .badge-xl
layout effect: badge width is 120
layout effect: badge width is 120Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Do slow work in a layout effect
Add a loop that runs for 300 ms (const end = performance.now() + 300; while (performance.now() < end) {}) inside the tooltip's layout effect.
Myth vs fact
Myth
useLayoutEffect is the "more correct" useEffect.
Fact
It's a specialised tool that blocks painting. Use useEffect unless you must measure or adjust layout before the user sees it.
Myth
useEffect always runs after paint, so it always flickers.
Fact
It usually runs after paint, and for updates from clicks React may even flush it before paint. The flicker risk is real but not guaranteed, which is why layout effects exist to remove it for layout work.
Myth
Layout effects run on the server and crash.
Fact
They simply don't run on the server. React 19 no longer even warns; you just get the first-guess HTML until hydration.
Myth
Apps should use useInsertionEffect for dynamic styles.
Fact
It's meant for CSS-in-JS library authors. In apps, use CSS classes, CSS variables or inline style.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Layout effects run in the same synchronous commit as DOM mutations: React walks the tree running insertion effects and DOM changes (mutation phase), switches the current tree, attaches refs and runs layout effects (layout phase), and only then yields. A
setStateinside a layout effect is processed synchronously (SyncLane) before React returns to the browser. - ▸
Reading layout (
getBoundingClientRect,offsetHeight) forces the browser to calculate layout immediately. Reading and writing alternately in a loop causes layout thrashing; batch reads, then writes. - ▸
useLayoutEffectcleanups run synchronously during the commit when a component unmounts, so they can still read the DOM nodes;useEffectcleanups run later. - ▸
React 19's ref callback cleanup and
<Activity>both interact with layout effects: hiding an Activity subtree runs layout effect cleanups (it's being visually hidden) and recreates them when shown.
Remember this
- 1
Layout is the browser's work of calculating the size and position of every element. Measuring means reading those numbers, for example with
element.getBoundingClientRect(), which returns itswidth,height,topandleftin pixels. Some UIs need a measurement to decide what to render: a tooltip must know its own height to choose whether to sit above or below a button. - 2
useLayoutEffect(setup, deps)has the same shape asuseEffect(setup, cleanup, dependencies), but different timing: it runs synchronously right after React commits the DOM changes, before the browser paints. If it sets state, React re-renders and commits again immediately, still before the paint. The user only ever sees the final, corrected layout. - 3
With
useEffectthe same code would usually run after the paint: the user might see one frame with the tooltip in the wrong place, then a jump. That visible jump is called a flicker (or layout shift). How often you see it depends on the device and the update; layout effects remove the risk. - 4
The cost: while a layout effect runs, and while the extra render it triggers runs, the browser can't paint. A slow layout effect makes the whole app feel stuck. So use
useEffectby default, anduseLayoutEffectonly when you must measure or change layout before the user sees it (tooltips, popovers, auto-sizing text, keeping scroll position). - 5
SSR caveat: on the server there is no layout and no DOM, so neither
useEffectnoruseLayoutEffectruns during server rendering (Phase 20). Server HTML is built with the initial state (the tooltip's first guess). React 18 printed a warning whenuseLayoutEffectwas used during server rendering; React 19 removed that warning, but the behaviour is the same: the measurement happens only after hydration in the browser. Fixes: render such components only on the client, or choose a default that looks right without measuring. - 6
**
useInsertionEffect(React 18) runs before any layout effects, at the start of the commit. It exists for authors of CSS-in-JS libraries** (libraries that generate CSS from JavaScript, like styled-components or Emotion) to insert<style>tags before layout effects read the layout, so measurements see the final styles. It can't update state and refs aren't attached yet. Application code almost never needs it.
Explain it without notes
How does useLayoutEffect differ from useEffect, and when would you choose it?
Explain how a tooltip can measure itself and reposition without the user seeing a jump.
What happens to useLayoutEffect during server rendering in React 19?
What is useInsertionEffect for, and why does it run before layout effects?
Practice
Write a component that renders a <textarea> and, in a layout effect depending on value, sets its height to its scrollHeight so it grows with the text.
Which hook would you use to log a page view, and which to scroll a chat list to the bottom before the new message is shown? Why?
Trade-offs
- ↔
useLayoutEffectremoves flicker but blocks painting; keep it short and only for layout work. - ↔
Client-only rendering avoids SSR mismatches for measured UI but means that part isn't in the server HTML (worse for SEO and first paint).
Done when you can
I can order insertion effects, layout effects, paint and effects.
I can measure an element and reposition it before paint.
I default to
useEffectand know whenuseLayoutEffectis worth its cost.I know layout effects don't run on the server.
I can explain who
useInsertionEffectis for.