UseEffect vs useLayoutEffect: Key Differences and When to Use Each
useEffect runs after the browser paints the screen, making it ideal for side effects that don't block rendering. useLayoutEffect runs synchronously before the browser paints, useful for DOM measurements or updates that must happen before the user sees the UI.Quick Comparison
Here is a quick side-by-side comparison of useEffect and useLayoutEffect to understand their main differences.
| Factor | useEffect | useLayoutEffect |
|---|---|---|
| Execution Timing | After browser paint | Before browser paint |
| Blocking UI | Does not block UI rendering | Blocks UI rendering until effect finishes |
| Use Case | Side effects like data fetching, subscriptions | DOM measurements, synchronously updating layout |
| Performance Impact | Better for non-blocking tasks | Can cause layout thrashing if overused |
| Common Pitfall | May cause flicker if updating layout | Can delay paint causing jank if heavy work |
Key Differences
useEffect runs its effect function after the browser has painted the UI. This means it does not block the user from seeing the initial render. It is perfect for side effects like fetching data, setting up subscriptions, or logging, where you don't need to block the UI.
In contrast, useLayoutEffect runs synchronously immediately after React has performed all DOM mutations but before the browser paints. This lets you read layout from the DOM and synchronously re-render if needed, preventing visual glitches. However, because it blocks painting, heavy work here can cause noticeable delays.
Choosing between them depends on whether your effect needs to happen before the user sees the UI (useLayoutEffect) or can happen after without blocking rendering (useEffect).
Code Comparison
This example uses useEffect to update the document title after the component renders.
import React, { useEffect, useState } from 'react'; function TitleUpdater() { const [count, setCount] = useState(0); useEffect(() => { document.title = `Count: ${count}`; }, [count]); return ( <div> <p>Count is {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); } export default TitleUpdater;
useLayoutEffect Equivalent
This example uses useLayoutEffect to measure a DOM element's width before the browser paints, ensuring layout updates happen synchronously.
import React, { useLayoutEffect, useRef, useState } from 'react'; function WidthMeasurer() { const ref = useRef(null); const [width, setWidth] = useState(0); useLayoutEffect(() => { if (ref.current) { setWidth(ref.current.getBoundingClientRect().width); } }, []); return ( <div> <div ref={ref} style={{ width: '50%' }}>Resize the window</div> <p>Measured width: {width}px</p> </div> ); } export default WidthMeasurer;
When to Use Which
Choose useEffect when your side effect does not need to block the browser paint, such as fetching data, event listeners, or logging. It keeps your app responsive and avoids blocking UI updates.
Choose useLayoutEffect when you need to read or modify the DOM layout synchronously before the user sees it, like measuring element sizes or synchronously applying styles to prevent flicker.
In general, prefer useEffect for most cases and only use useLayoutEffect when you must avoid visual glitches caused by layout changes.
Key Takeaways
useEffect runs after paint and is best for non-blocking side effects.useLayoutEffect runs before paint and is for DOM reads or synchronous layout updates.useEffect by default and switch to useLayoutEffect only to prevent flicker or layout glitches.useLayoutEffect can block rendering and cause jank if overused.