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
- The timing: after paint vs before paint
- Measure DOM size and position safely
- Avoid layout flicker with a pre-paint effect
- When NOT to use useLayoutEffect
- Why it can hurt performance
- The SSR warning and how to handle it
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 flashYour 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
// useEffect3️⃣ 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)
- Visible flicker when positioning. — You measured in useEffect. Move the measure-and-adjust into useLayoutEffect.
- Janky, slow UI. — Heavy work in useLayoutEffect blocks paint. Keep it tiny or move it to useEffect.
- SSR warning in the console. — useLayoutEffect on the server. Use useEffect or a client-only guard.
- Reaching for it by default. — Most effects do not need pre-paint timing. Default to useEffect.
📋 Quick Reference
| Aspect | useEffect | useLayoutEffect |
|---|---|---|
| Timing | after paint | before paint |
| Sync? | async | synchronous |
| Use for | fetch, subscribe, log | measure + adjust layout |
| SSR | fine | warns (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
- ✅ useEffect runs after paint; useLayoutEffect runs before paint
- ✅ Use the layout hook to measure and adjust the DOM without flicker
- ✅ It runs synchronously, so keep the work light
- ✅ Default to useEffect; mind the SSR warning
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
- Previous: Strict Mode & Double-Render
- Next: Checkpoint: React Fundamentals — Combine JSX, props, rendering and immutable state in a build — then a quiz
- Quick reference: React cheat sheet
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.