← Back to Chapters

React useLayoutEffect() Hook

⚛️ React useLayoutEffect() Hook

? Quick Overview

⚛️ 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.

  • Ideal for layout measurements, synchronized animations, and DOM tweaks.
  • Runs earlier than useEffect(), so it can block paint if overused.
  • Should be used only when you truly need layout-synchronous logic.

? Key Concepts

  • Timing: Runs after DOM mutations but before the browser paints the new frame.
  • Synchronous: Blocks painting until the effect callback finishes.
  • DOM-focused: Great for reading sizes, positions, and scrolling values.
  • Cleanup: You can return a cleanup function to reset styles, listeners, or timers.
  • Performance-sensitive: Heavy work here can make the UI feel janky or frozen.

? Syntax & Behavior

The signature of useLayoutEffect() is the same as useEffect(), but its execution timing is different.

? View Code Example
// 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]);
  • Runs after React updates the DOM, but before the browser performs the paint.
  • Use dependencies to control when the effect should re-run.
  • Use cleanup to avoid memory leaks and to reset any DOM changes or listeners.

? Example 1: Measuring DOM Layout

In this example, we measure the width of a box right after it renders, using getBoundingClientRect().

? View Code Example
// 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.

? Example 2: useEffect vs useLayoutEffect

This demo logs when each Hook runs so you can compare their timing in the console.

? View Code Example
// 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.

? Example 3: Synchronized Animation

Here we use useLayoutEffect() to synchronize inline styles for a smooth resize animation.

? View Code Example
// 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.

? useEffect vs useLayoutEffect Comparison

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

? Live Output & Explanation

  • Box measurement: The console shows the box width immediately after render, so the value always matches what the user sees.
  • Timing demo: In DevTools, observe that useLayoutEffect logs appear before useEffect logs.
  • Animated box: Each click on Grow Box resizes the square smoothly because layout-related style changes are applied before paint.

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.

? Tips & Best Practices

  • Prefer useEffect() for most side effects (API calls, logging, non-visual updates).
  • Reserve useLayoutEffect() for DOM measurements, scroll position sync, and layout-sensitive animations.
  • Keep the callback work lightweight to avoid blocking paint and making the UI feel frozen.
  • Always provide a cleanup function when you add event listeners, timers, or temporary styles.
  • In SSR (Server-Side Rendering) environments, prefer useEffect() or conditionally guard usage to avoid warnings.

? Try It Yourself / Practice Tasks

  1. Build two components — one using useEffect(), one using useLayoutEffect() — and compare their console logs during a re-render.
  2. Create a resizable box that shows its current width and height using useLayoutEffect() after each resize.
  3. Implement a smooth resize or fade animation that updates inline styles in useLayoutEffect() and logs layout values before every frame.
  4. Use DevTools to inspect how different effect timings impact perceived performance and flicker.

Goal: Become comfortable choosing between useEffect() and useLayoutEffect() based on whether your effect logic truly depends on precise layout timing.