Suggest an editImprove this articleRefine the answer for “What is the difference between useEffect and useLayoutEffect?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**useEffect vs useLayoutEffect**: `useEffect` runs after the browser paints the screen (async); `useLayoutEffect` runs before paint, blocking the browser until the callback finishes (sync). ```jsx useEffect(() => { /* post-paint */ }, [dep]); // APIs, timers, subscriptions useLayoutEffect(() => { /* pre-paint */ }, [dep]); // DOM measurements, flicker fixes ``` **Key rule:** use `useLayoutEffect` only when you need DOM measurements or must prevent a visible flicker. Everything else uses `useEffect`.Shown above the full answer for quick recall.Answer (EN)Image**useEffect vs useLayoutEffect** - the only difference is timing: one runs after the browser paints, the other blocks paint until it finishes. ## Theory ### TL;DR - `useEffect` fires after the browser paints the screen (async, post-paint) - `useLayoutEffect` fires before paint, blocking the browser until the callback finishes (sync, pre-paint) - Analogy: `useEffect` is the cleanup crew arriving after guests already saw the mess; `useLayoutEffect` cleans up before anyone walks in - Need DOM measurements or a no-flicker correction? Use `useLayoutEffect`. Everything else goes to `useEffect` - `useLayoutEffect` warns in SSR (Node.js); `useEffect` does not ### Quick example ```jsx function Box() { const [count, setCount] = useState(0); const ref = useRef(); // Runs AFTER paint - user sees old width first, then it jumps useEffect(() => { ref.current.style.width = `${count * 20}px`; }, [count]); // Swap to this - width is set before paint, no flicker // useLayoutEffect(() => { // ref.current.style.width = `${count * 20}px`; // }, [count]); return ( <div ref={ref} style={{ background: '#eee' }} onClick={() => setCount(c => c + 1)}> Click: {count} </div> ); } ``` With `useEffect`, the browser paints the old width first, then the effect fires and the box jumps. Swap to `useLayoutEffect` and the width is corrected before a single pixel is drawn. ### Key difference Both hooks accept the same signature, and cleanup works identically. The only real difference is when they run inside React's [commit phase](/questions/react-render-commit-phases). `useLayoutEffect` fires synchronously after DOM mutations but before the browser paints. `useEffect` is scheduled post-paint at a lower priority. That gap is milliseconds small, but visible to the eye whenever DOM measurements or style corrections are involved. I have seen this exact issue in production where a tooltip positioned with `useEffect` would visibly snap into place on every render. ### When to use - DOM measurements (width, height, scroll position) before render → `useLayoutEffect` - Correcting layout to prevent a visible jump or flicker → `useLayoutEffect` - Animations that read layout before writing values → `useLayoutEffect` - Data fetching, timers, event listeners, subscriptions → `useEffect` - Anything that does not touch visible DOM layout → `useEffect` ### Comparison table | Aspect | useEffect | useLayoutEffect | |---|---|---| | Timing | After paint (async) | Before paint (sync) | | Blocks browser paint? | No | Yes | | DOM layout reads | Causes flicker | Safe, matches visible state | | Performance risk | Minimal | Can delay render if callback is heavy | | SSR (Node.js) | Safe | Prints a warning | | Typical use | APIs, subscriptions, timers | DOM measurements, style corrections | ### How it works internally During React's commit phase, `useLayoutEffect` callbacks run synchronously right after DOM mutations, before the browser paints. `useEffect` callbacks are queued after paint via React's internal Scheduler at a lower priority. In React 18 concurrent mode, `useLayoutEffect` still fires synchronously and will block transitions. For non-urgent work inside a [startTransition](/questions/usetransition) call, keep it in `useEffect`. ### Common mistakes **Using `useEffect` for scroll corrections:** ```jsx // Wrong: user sees top of page first, then it jumps useEffect(() => { ref.current.scrollTop = 100; }, []); // Correct: scroll happens before first paint useLayoutEffect(() => { ref.current.scrollTop = 100; }, []); ``` **Putting heavy computation inside `useLayoutEffect`:** ```jsx // Wrong: blocks paint for 100ms+, UI appears frozen useLayoutEffect(() => { for (let i = 0; i < 1_000_000; i++) { /* heavy work */ } }, []); ``` Keep `useLayoutEffect` callbacks under 5ms. Move anything heavier to `useEffect`. **Forgetting SSR compatibility:** `useLayoutEffect` triggers a warning in Node.js: `Warning: useLayoutEffect does nothing on the server`. If the component renders on the server (Next.js, Remix), switch to `useEffect` or guard with `typeof window !== 'undefined'`. ### Real-world usage - Framer Motion reads DOM layout in `useLayoutEffect` before writing animation values - Next.js `<Image>` uses `useLayoutEffect` for placeholder sizing - TanStack Query v5 uses `useEffect` for fetches, `useLayoutEffect` for table cache sync - React DevTools uses `useLayoutEffect` internally for inspector measurements - Redux Toolkit uses `useEffect` for store subscriptions ### Follow-up questions **Q:** Can `useLayoutEffect` cause performance problems? **A:** Yes. It blocks paint, so if the callback takes more than a few milliseconds the browser frame is delayed and the UI feels janky. Chrome DevTools flags this as a long task. **Q:** What happens to `useLayoutEffect` in SSR? **A:** React prints a warning and the hook does nothing on the server. `useEffect` is SSR-safe because it only runs in the browser. **Q:** What is the cleanup order for both hooks? **A:** Both support cleanup functions. `useLayoutEffect` cleanup runs synchronously before the next layout effect fires. `useEffect` cleanup runs asynchronously before the next effect. **Q:** In React 18 concurrent mode, does `useLayoutEffect` still block? **A:** Yes, it remains synchronous and pre-paint even in concurrent mode. `startTransition` defers state updates, but `useLayoutEffect` still fires before paint. Use `useEffect` for non-urgent side effects inside transitions. ## Examples ### Measuring element height before paint ```jsx function TodoItem({ text, onDelete }) { const ref = useRef(); const [height, setHeight] = useState(0); useLayoutEffect(() => { // Measure before paint - no visible layout jump setHeight(ref.current.getBoundingClientRect().height); }); return ( <div ref={ref} style={{ minHeight: `${height}px`, transition: 'height 0.2s' }}> {text} <button onClick={onDelete}>Delete</button> </div> ); } ``` `getBoundingClientRect` reads real DOM dimensions. In `useEffect` the browser would paint the wrong height first, causing a visible jump on add or delete. `useLayoutEffect` measures and corrects before the user sees anything. ### Data fetching with useEffect ```jsx function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { let cancelled = false; fetch(`/api/users/${userId}`) .then(res => res.json()) .then(data => { if (!cancelled) setUser(data); }); return () => { cancelled = true; }; }, [userId]); if (!user) return <p>Loading...</p>; return <p>{user.name}</p>; } ``` Data fetching has nothing to do with pre-paint layout. `useEffect` is correct here. The `cancelled` flag prevents a state update on an unmounted component, which is a common source of memory leak warnings in React DevTools.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.