Skip to content

useLayoutEffect Hook ​

  • Has the exact same signature as useEffect
  • The difference is when it runs — useLayoutEffect runs synchronously after DOM mutations but before the browser paints
js
useLayoutEffect(() => {
    // runs after DOM update, before browser paint
    return () => {
        // cleanup
    };
}, [dependencies]);
  • The order in which multiple useLayoutEffect hooks are executed is determined by the order in which they were called.

Execution Order ​

State changes / Component renders
        ↓
React updates the DOM (commit phase)
        ↓
useLayoutEffect runs  ← synchronous, blocks paint
        ↓
Browser paints (user sees the update)
        ↓
useEffect runs  ← asynchronous, after paint
jsx
function Component() {
    useEffect(() => {
        console.log("2 — useEffect");
    });

    useLayoutEffect(() => {
        console.log("1 — useLayoutEffect");
    });

    return <div>Hello</div>;
}

// Output:
// 1 — useLayoutEffect
// 2 — useEffect

Why It Exists — The Flash Problem ​

useEffect runs after the browser paints — if you use it to measure or adjust the DOM, the user sees the old layout for one frame before your adjustment applies.

jsx
function Tooltip({ target }) {
    const [position, setPosition] = useState({ top: 0, left: 0 });
    const tooltipRef = useRef(null);

    useEffect(() => {
        const rect = tooltipRef.current.getBoundingClientRect();
        // if tooltip goes off screen, reposition it
        if (rect.right > window.innerWidth) {
            setPosition({ top: 0, left: -rect.width }); // triggers re-render
        }
    }, []);

    return (
        <div ref={tooltipRef} style={position}>
            Tooltip content
        </div>
    );
}
First paint  → tooltip renders at wrong position  ← user sees this flash
useEffect    → position corrected
Second paint → tooltip renders at correct position

With useLayoutEffect:

jsx
useLayoutEffect(() => {
    const rect = tooltipRef.current.getBoundingClientRect();
    if (rect.right > window.innerWidth) {
        setPosition({ top: 0, left: -rect.width });
    }
}, []);
DOM updated  → useLayoutEffect runs → position corrected → paint
             ← user never sees the wrong position

useEffect vs useLayoutEffect ​

useEffectuseLayoutEffect
RunsAfter browser paintAfter DOM update, before paint
Blocks paintNoYes
Use forData fetching, subscriptions, loggingDOM measurement, position correction
Performance riskNoneCan delay paint if work is heavy

When to Use useLayoutEffect ​

Only when you need to read or mutate the DOM before the user sees it.

Common cases:

1. Measuring DOM elements

jsx
function Component() {
    const ref = useRef(null);
    const [width, setWidth] = useState(0);

    useLayoutEffect(() => {
        setWidth(ref.current.getBoundingClientRect().width);
        // measured before paint — no flash
    }, []);

    return <div ref={ref}>Width is {width}px</div>;
}

2. Fixing scroll position

jsx
useLayoutEffect(() => {
    window.scrollTo(0, 0); // reset scroll before user sees new page
}, [pathname]);

3. Synchronizing with third party DOM libraries

jsx
useLayoutEffect(() => {
    const chart = new ChartLibrary(ref.current, data);
    // library reads DOM immediately — must be in sync before paint
    return () => chart.destroy();
}, [data]);

useLayoutEffect Blocks Paint — Use Sparingly ​

Since useLayoutEffect is synchronous and blocks the browser from painting, heavy work inside it directly delays what the user sees.

jsx
// Bad — heavy work inside useLayoutEffect delays paint
useLayoutEffect(() => {
    for (let i = 0; i < 1000000; i++) {
        // expensive loop
    }
    setPosition(calculate());
}, []);

// Better — only put the DOM read/write inside useLayoutEffect
// move expensive calculation outside
const position = calculate(); // outside

useLayoutEffect(() => {
    ref.current.style.top = position.top + "px"; // only DOM mutation here
}, [position]);

Server-Side Rendering (SSR) Warning ​

useLayoutEffect does not run on the server — React will warn you if you use it in a server rendered component.

Warning: useLayoutEffect does nothing on the server because its effect
cannot be encoded into the server renderer's output format.
jsx
// If you need to use it in an SSR context, guard it
const useIsomorphicLayoutEffect =
    typeof window !== "undefined" ? useLayoutEffect : useEffect;

useIsomorphicLayoutEffect(() => {
    // safe in both SSR and client
}, []);

Summary ​

Need to run after render?
        ↓
Will it read or mutate the DOM before paint?
        ↓
    Yes → useLayoutEffect
    No  → useEffect

Default to useEffect always — reach for useLayoutEffect only when you can actually see a flash or layout jump that needs to be fixed.