⚛️ React Hook
useLayoutEffect() is a React Hook that runs synchronously after React has updated the DOM but before the browser paints the screen. This gives you a small window to read layout values or adjust the DOM before the user actually sees the update.
useEffect(), so it can block paint if overused.The signature of useLayoutEffect() is the same as useEffect(), but its execution timing is different.
// Basic useLayoutEffect syntax with cleanup
useLayoutEffect(() => {
// Read layout or perform DOM updates before paint
return () => {
// Cleanup runs before the next layout effect or unmount
};
}, [dependencies]);
In this example, we measure the width of a box right after it renders, using getBoundingClientRect().
// Measure a box width immediately after layout
function Box() {
const boxRef = React.useRef(null);
React.useLayoutEffect(() => {
const box = boxRef.current;
console.log("Box width:", box.getBoundingClientRect().width);
// Logs the final width before the browser paints
});
return (
<div
ref={boxRef}
style={{
width: "200px",
height: "100px",
backgroundColor: "#007bff",
margin: "20px auto"
}}
></div>
);
}
Because useLayoutEffect() runs before paint, the width you read is fully up to date with the latest DOM changes, and the user never sees any intermediate flicker.
This demo logs when each Hook runs so you can compare their timing in the console.
// Compare timing of useEffect and useLayoutEffect
function Demo() {
React.useEffect(() => {
console.log("? useEffect called (after paint)");
});
React.useLayoutEffect(() => {
console.log("⚡ useLayoutEffect called (before paint)");
});
return <h5 className="text-center">Check the console logs</h5>;
}
In the console, you will see the useLayoutEffect log first, followed by the useEffect log. This confirms that useLayoutEffect() blocks paint until it finishes, whereas useEffect() runs later, asynchronously.
Here we use useLayoutEffect() to synchronize inline styles for a smooth resize animation.
// Smoothly grow a box by syncing styles before paint
function AnimatedBox() {
const boxRef = React.useRef(null);
const [size, setSize] = React.useState(100);
React.useLayoutEffect(() => {
const el = boxRef.current;
el.style.transition = "all 0.5s ease";
el.style.width = size + "px";
el.style.height = size + "px";
}, [size]);
return (
<div className="text-center">
<div
ref={boxRef}
style={{ backgroundColor: "#28a745", margin: "20px auto" }}
></div>
<button className="btn btn-primary" onClick={() => setSize(size + 50)}>
Grow Box
</button>
</div>
);
}
Because the style changes are applied in useLayoutEffect(), the browser can compute the new layout before painting, producing a smooth transition with no visible jump in size.
| Aspect | useEffect() | useLayoutEffect() |
|---|---|---|
| Timing | After paint | Before paint |
| Execution | Asynchronous | Synchronous |
| Typical Use | APIs, logging, non-blocking work | DOM measurements, layout-sensitive UI |
| Performance | Preferred for most side effects | Can block paint; use sparingly |
useLayoutEffect logs appear before useEffect logs.In all these cases, useLayoutEffect() ensures that layout-critical logic happens at the tightest point in the render–paint cycle, which is why it must be used with care.
useEffect() for most side effects (API calls, logging, non-visual updates).useLayoutEffect() for DOM measurements, scroll position sync, and layout-sensitive animations.useEffect() or conditionally guard usage to avoid warnings.useEffect(), one using useLayoutEffect() — and compare their console logs during a re-render.useLayoutEffect() after each resize.useLayoutEffect() and logs layout values before every frame.Goal: Become comfortable choosing between useEffect() and useLayoutEffect() based on whether your effect logic truly depends on precise layout timing.