Command Palette

Search for a command to run...

Hectal
PHASE 7Intermediate ~21 min· topic 8 of 9

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.

The three effect hooks run at different momentsdiagram
Rendering diagram…

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.

TooltipButton.jsxwhole filejsx
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.

TooltipButton.jsxwhole filejsx
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.

ClientOnly.jsxwhole filejsx
function ClientOnly({ children }) {
  const [mounted, setMounted] = useState(false);
  useEffect(() => setMounted(true), []);
  return mounted ? children : null;
}

Try it yourself

  1. 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. 2

    Swap to useEffect

    Change useLayoutEffect to useEffect in 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. 3

    Make the measurement wrong

    In the insertion example, change useInsertionEffect to useEffect inside useCss. Predict the widths now. (The layout effects run first, before the style exists, so they measure the unstyled span's width.)

Code & diagrams

The order: insertion, layout, paint, effect New tab

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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

render, h = 0
<div style="height:40px">box</div>
--- client
render, h = 0
insertion effect
layout effect
effect
Measure a tooltip before the browser paints New tab

The 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

Press Run to see the page.

Console

Nothing logged yet.

Expected console output

render, placement = above
layout effect: tip is 40px tall, 16px of space above
render, placement = below
useInsertionEffect: styles in place before anyone measures React 18+ New tab

Each `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

Press Run to see the page.

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 120

Break 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.

terminal
$ Preview
── what you'll see ──
The whole page appears 300 ms late, and every interaction that triggers the effect freezes the screen for 300 ms. Nothing is logged as an error.

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 setState inside 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.

  • ▸

    useLayoutEffect cleanups run synchronously during the commit when a component unmounts, so they can still read the DOM nodes; useEffect cleanups 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. 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 its width, height, top and left in 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. 2

    useLayoutEffect(setup, deps) has the same shape as useEffect (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. 3

    With useEffect the 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. 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 useEffect by default, and useLayoutEffect only when you must measure or change layout before the user sees it (tooltips, popovers, auto-sizing text, keeping scroll position).

  5. 5

    SSR caveat: on the server there is no layout and no DOM, so neither useEffect nor useLayoutEffect runs during server rendering (Phase 20). Server HTML is built with the initial state (the tooltip's first guess). React 18 printed a warning when useLayoutEffect was 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. 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

01

How does useLayoutEffect differ from useEffect, and when would you choose it?

02

Explain how a tooltip can measure itself and reposition without the user seeing a jump.

03

What happens to useLayoutEffect during server rendering in React 19?

04

What is useInsertionEffect for, and why does it run before layout effects?

Practice

01

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.

02

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

  • ↔

    useLayoutEffect removes 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 useEffect and know when useLayoutEffect is worth its cost.

  • I know layout effects don't run on the server.

  • I can explain who useInsertionEffect is for.