useLayoutEffect vs useEffect

Reviewed & published by Brayan K

useLayoutEffect is a React Hook that runs synchronously after the DOM is updated but before the browser paints, whereas useEffect runs asynchronously after paint. You reach for useLayoutEffect only when you must measure layout and adjust it before the user sees a flicker.

Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You'll Learn

1️⃣ The Timing Difference

After a render, React mutates the DOM. Then useLayoutEffect fires synchronously, before the browser paints — so any DOM change you make is included in the very first frame the user sees. The browser paints, and only then does useEffect fire asynchronously, after paint. That single ordering difference is the whole lesson.

function Box() {
  const ref = useRef(null);

  useLayoutEffect(() => {
    // runs BEFORE paint — measure & adjust with no flicker
    const width = ref.current.offsetWidth;
    ref.current.style.setProperty("--w", width + "px");
  });

  useEffect(() => {
    // runs AFTER paint — good for logging, fetching, subscriptions
  });

  return <div ref={ref} />;
}
// TIMING order for one update:
// 1) render -> 2) React mutates DOM -> 3) useLayoutEffect (before paint)
//    -> 4) browser PAINTS -> 5) useEffect (after paint)
const steps = [];
function commitDom()       { steps.push("dom-updated"); }
function layoutEffect()    { steps.push("useLayoutEffect (pre-paint)"); }
function paint()           { steps.push("paint"); }
function passiveEffect()   { steps.push("useEffect (post-paint)"); }

commitDom(); layoutEffect(); paint(); passiveEffect();

console.log(steps.join(" -> "));
// dom-updated -> useLayoutEffect (pre-paint) -> paint -> useEffect (post-paint)

2️⃣ Measuring Layout & Avoiding Flicker

The classic use case is reading a DOM measurement (width, height, scroll position) and then immediately changing the layout based on it — like positioning a tooltip above its anchor. If you did this in useEffect, the element would paint in the wrong spot for one frame and then jump, producing a visible flicker. Doing it in useLayoutEffect fixes the position before that first paint.

function Tooltip({ anchorRef }) {
  const tipRef = useRef(null);
  useLayoutEffect(() => {
    const a = anchorRef.current.getBoundingClientRect();
    const h = tipRef.current.offsetHeight;
    tipRef.current.style.top = (a.top - h) + "px";   // above the anchor
  });
  return <div ref={tipRef} className="tooltip" />;
}
// FLICKER happens when you change layout AFTER paint (useEffect):
// the user sees the wrong position for one frame, then a jump.
function frames(usePrePaint) {
  // measured width is 0 until DOM exists; we correct it to 120
  if (usePrePaint) return ["120"];               // fixed before paint: no flicker
  return ["0 (visible!)", "120"];                // fixed after paint: flicker
}

console.log(frames(true).join(", "));   // 120
console.log(frames(false).join(", "));  // 0 (visible!), 120  <- the flash

Your turn — pick the right hook:

// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// Decide which hook to use. Rule: choose useLayoutEffect ONLY when you must
// measure and synchronously adjust layout to avoid flicker; else useEffect.
function chooseHook(needsSyncLayoutMeasure) {
  return needsSyncLayoutMeasure ___ "useLayoutEffect" ___ "useEffect"; // 👉 ? :
}

console.log(chooseHook(true));    // useLayoutEffect
console.log(chooseHook(false));   // useEffect

// ✅ Expected output:
//    useLayoutEffect
//    useEffect

3️⃣ When NOT to Use It & the SSR Warning

Because useLayoutEffect runs synchronously before paint, heavy work inside it blocks painting and makes the UI feel sluggish. So default to useEffect for data fetching, subscriptions, logging, and anything that does not need to happen before paint. Also, on the server there is no DOM and no paint, so React warns about useLayoutEffect during SSR — use useEffect there, or run the layout effect only on the client.

// ❌ Don't: fetch in a layout effect (blocks paint, no DOM need)
// useLayoutEffect(() => { fetch(url); });

// ✅ Do: fetch in a passive effect
useEffect(() => {
  const ctrl = new AbortController();
  fetch(url, { signal: ctrl.signal });
  return () => ctrl.abort();
}, [url]);

// SSR-safe layout work: guard for the client
const isClient = typeof window !== "undefined";

🧩 Reorder Challenge

These steps are the lifecycle of one update with both hooks. Put them in the right order:

A.   The browser paints the frame
B.   React renders the component to a virtual DOM
C.   useEffect runs (asynchronously)
D.   React mutates the real DOM
E.   useLayoutEffect runs (synchronously)

Common Errors (and the fix)

📋 Quick Reference

AspectuseEffectuseLayoutEffect
Timingafter paintbefore paint
Sync?asyncsynchronous
Use forfetch, subscribe, logmeasure + adjust layout
SSRfinewarns (client only)

🧠 Quick Recall

1. Which hook runs before paint?

useLayoutEffect — synchronously, after DOM mutation but before the browser paints.

2. Why default to useEffect?

It runs after paint and does not block rendering, keeping the UI responsive. Only measure-and-adjust layout needs pre-paint timing.

3. Why the SSR warning?

There is no DOM or paint on the server, so a layout effect cannot run meaningfully. Use useEffect or a client guard.

Mini-Challenge: Position Above the Anchor

Compute where a tooltip's top should sit so it appears above each anchor — the kind of measurement that belongs in a pre-paint effect. Run it and check your output.

// 🎯 MINI-CHALLENGE: position a tooltip without flicker
//
// To place a tooltip above an element you need its top minus the tooltip
// height. Doing this in useLayoutEffect (pre-paint) avoids a visible jump.
// 1. Write topFor(elementTop, tooltipHeight) -> the correct top.
// 2. Compute positions for two anchors and console.log them.
//
// ✅ Expected output:
//    70
//    180

const anchors = [
  { top: 100, tipHeight: 30 },
  { top: 200, tipHeight: 20 },
];

// your code here

🎉 Lesson Complete

Practice quiz

When does useLayoutEffect run relative to the browser paint?

  • Asynchronously, after the browser paints
  • Before React renders the component
  • Synchronously, after DOM mutation but before the browser paints
  • Only on the server during SSR

Answer: Synchronously, after DOM mutation but before the browser paints. useLayoutEffect fires synchronously after React mutates the DOM but before paint, so DOM changes appear in the first frame the user sees.

When does useEffect run relative to the browser paint?

  • Asynchronously, after the browser paints
  • Synchronously, before paint
  • During the render phase
  • It never runs in the browser

Answer: Asynchronously, after the browser paints. useEffect runs asynchronously after the browser has painted, which keeps the UI responsive and avoids blocking rendering.

Which hook should you reach for to measure the DOM and adjust layout without a visible flicker?

  • useEffect
  • useMemo
  • useCallback
  • useLayoutEffect

Answer: useLayoutEffect. Because useLayoutEffect runs before paint, you can measure and synchronously adjust layout so the user never sees the intermediate position.

Why is useEffect the recommended default over useLayoutEffect?

  • useEffect can read the DOM but useLayoutEffect cannot
  • useLayoutEffect runs synchronously before paint and can block rendering
  • useEffect runs more times per render
  • useLayoutEffect does not support cleanup functions

Answer: useLayoutEffect runs synchronously before paint and can block rendering. useLayoutEffect blocks painting while it runs. Heavy work there makes the UI sluggish, so default to useEffect unless you must adjust layout pre-paint.

Why does React warn about useLayoutEffect during server-side rendering?

  • There is no DOM or paint on the server, so it cannot run meaningfully
  • It runs too many times on the server
  • It conflicts with useEffect on the server
  • It requires a network connection

Answer: There is no DOM or paint on the server, so it cannot run meaningfully. On the server there is no DOM to lay out and no paint, so useLayoutEffect can't do its job. Use useEffect or guard it to run only on the client.

What is the correct order of events for a single update with both hooks?

  • render → useEffect → paint → DOM mutation → useLayoutEffect
  • useEffect → render → paint → useLayoutEffect → DOM mutation
  • render → DOM mutation → useLayoutEffect → paint → useEffect
  • paint → render → useLayoutEffect → useEffect → DOM mutation

Answer: render → DOM mutation → useLayoutEffect → paint → useEffect. React renders, mutates the DOM, runs useLayoutEffect (pre-paint), the browser paints, then useEffect runs (post-paint).

Where should you put a data fetch — useEffect or useLayoutEffect?

  • useLayoutEffect, so it runs sooner
  • useEffect, since fetching doesn't need pre-paint timing and shouldn't block paint
  • Either one; there is no difference
  • Neither — fetch during render

Answer: useEffect, since fetching doesn't need pre-paint timing and shouldn't block paint. Fetching has no DOM-layout need and blocking paint hurts performance, so it belongs in useEffect (after paint).

What causes a visible 'flicker' when positioning a tooltip?

  • Using useLayoutEffect to measure before paint
  • Calling the hook at the top level
  • Returning a cleanup function
  • Measuring and adjusting layout in useEffect, after the first paint

Answer: Measuring and adjusting layout in useEffect, after the first paint. If you measure and move the element in useEffect, it paints in the wrong spot for one frame then jumps. Pre-paint useLayoutEffect avoids that.

Both hooks support which of the following?

  • Only useLayoutEffect supports a cleanup function
  • Both accept a setup function that can return a cleanup function
  • Only useEffect supports a dependency array
  • Neither can be given a dependency array

Answer: Both accept a setup function that can return a cleanup function. useEffect and useLayoutEffect share the same signature: a setup function (optionally returning cleanup) plus an optional dependency array.

Because useLayoutEffect runs synchronously before paint, what guidance follows?

  • Put as much heavy work in it as possible
  • Never return a cleanup function from it
  • Keep the work light so it doesn't delay painting
  • Always pass an empty dependency array

Answer: Keep the work light so it doesn't delay painting. Anything in useLayoutEffect delays the paint, so keep it small — just the measure-and-adjust work that must happen before the user sees the frame.

Continue this course

Frequently asked questions

What is the core difference between the two?

Timing. useEffect runs asynchronously AFTER the browser paints. useLayoutEffect runs synchronously BEFORE paint, after React has updated the DOM. So useLayoutEffect can read and change layout before the user sees anything.

When should I prefer useLayoutEffect?

When you must measure the DOM (size, position) and then synchronously change it to avoid a visible flicker — for example positioning a tooltip relative to an element. Otherwise, default to useEffect.

Why is useLayoutEffect discouraged by default?

Because it runs synchronously before paint, it can block painting and hurt performance if your work is heavy. useEffect lets the browser paint first, keeping the UI responsive.

Why does useLayoutEffect warn during SSR?

On the server there is no DOM to lay out and no paint, so useLayoutEffect cannot run meaningfully and React warns. Use useEffect for SSR, or guard the layout effect to run only on the client.