Menu

React useLayoutEffect: Measure Before the Browser Paints

useLayoutEffect works like useEffect but runs right after React updates the DOM and before the browser paints. Use it to measure an element and adjust layout without a visible flicker, and keep useEffect for everything else.

This page includes runnable editors - edit, run, and see output instantly.

useLayoutEffect is a version of useEffect that runs earlier: right after React has updated the DOM, but before the browser paints the new frame. Code in it can measure elements and change the layout, and the user only ever sees the final result. It blocks painting while it runs, so use it only when you need that timing.

The effects are declared in the order effect, layout effect, but the console prints the layout effect first on every render. Declaration order does not matter between the two hooks; their timing does.

The syntax

The signature is identical to useEffect: a setup function, an optional cleanup, and a dependency array.

useLayoutEffect(() => {
    // runs after the DOM update, before paint
    return () => {
        // cleanup, before the next run and on unmount
    };
}, [dependencies]);

Everything you know about dependencies and cleanup from useEffect applies. The only difference is when React calls it.

When each one runs

After a render, React updates the DOM and then does three things in a fixed order:

  1. Runs all layout effect cleanups and layout effects, synchronously.
  2. Lets the browser paint. If a layout effect set state, React first renders again, still before paint.
  3. Runs regular effects, usually after paint.

Across a tree, every layout effect runs before any regular effect, and children run before their parents, since a parent can only finish once its children are in place:

The console shows both layout effects first, Child then Parent, then both regular effects in the same order. The key={n} makes every click mount a fresh Parent, so you get the same four lines each time. Add a cleanup that logs to each effect and watch the old ones run before the new ones.

The flicker problem

Picture a tooltip that should appear above its button, unless there is no room above, in which case it goes below. You cannot know whether there is room until the tooltip is in the DOM and you can measure its height. So rendering happens in two passes:

  1. Render the tooltip somewhere, so it can be measured.
  2. Measure it, compute the real position, set state, render again.

With useEffect, the browser may paint between those passes, and the user sees the tooltip jump from the first position to the second. With useLayoutEffect, the second pass happens before paint, so the first position is never shown.

Hover the top button: there is no room above it, so the tooltip appears below. Hover the lower one and it appears above. The console tells you which side the measurement picked. Change useLayoutEffect to useEffect and hover again: the tooltip can now flash at the top of the box for one frame before it moves. On a fast machine it is often too quick to notice, which is exactly why this bug gets shipped and then reported from slower phones.

offsetHeight, offsetTop and getBoundingClientRect() all force the browser to calculate layout, so reading them in the layout effect gives you the real values of the DOM React just wrote.

Measuring an element

The same pattern works for any size the render depends on: read it with a ref in a layout effect, store it in state, render with it.

Click the button and the width line updates in the same frame as the text. The page never shows a new text next to an old width, because the second render happens before paint. Remove [text] from the dependency array and the measurement runs after every render, including the one its own setWidth causes; it stops there only because the width no longer changes.

Prefer useEffect

A layout effect holds up the paint until it returns, and if it sets state, until the extra render finishes too. A slow one makes every update on the page feel slow. Use useLayoutEffect only when all of these are true:

  • The effect reads layout (size, position, scroll).
  • It changes what is rendered based on that reading.
  • The intermediate state would be visible and look wrong.

Everything else (fetching, subscriptions, timers, logging, analytics) belongs in useEffect. A quick rule: start with useEffect, and switch only if you can see a flicker.

useEffect usually runs after paint, but not always. When the update comes from a discrete user action like a click or a key press, React may run the effects before the browser paints. That is one reason a flicker can appear in one situation and not another.

Server rendering

Effects never run during server rendering, and neither does useLayoutEffect. The server sends the HTML of the first render, and the layout effect runs only after hydration in the browser. React 18 and earlier warned "useLayoutEffect does nothing on the server" when such a component was rendered on the server; React 19 dropped that warning.

If the first server-rendered frame would look wrong without the measurement, there are two common fixes: render a neutral fallback (for example, hide the tooltip until it is measured), or render the component only on the client:

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

useInsertionEffect

There is a third effect hook, useInsertionEffect, which runs before any layout effect. It exists for CSS-in-JS libraries that generate <style> tags at runtime: inserting the styles there means a layout effect that measures an element already sees it styled. It cannot update state and refs are not attached yet, so it is of no use in application code. If you are not writing a styling library, you will not need it.

Frequently Asked Questions

What is the difference between useEffect and useLayoutEffect?

Timing. useLayoutEffect runs synchronously after React has changed the DOM but before the browser paints, so its state updates are applied before the user sees anything. useEffect usually runs after paint. The API and the dependency array are the same.

When should I use useLayoutEffect?

When an effect reads layout (a size or a position) and immediately changes what is rendered based on it, such as placing a tooltip above or below its button. Without it the user can see the first, wrong position for a frame.

Is useLayoutEffect bad for performance?

It blocks the browser from painting until it finishes, along with any re-render it triggers. A slow layout effect makes the whole page feel slow, so use useEffect unless you need to avoid a visible flicker.

Does useLayoutEffect run on the server?

No. Neither effect runs during server rendering. Older React versions printed a warning when a component with useLayoutEffect was rendered on the server; React 19 no longer does, but the effect still only runs in the browser.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED