useLayoutEffect Hook
useLayoutEffect Hook — Synchronous update of DOM before rendering
useLayoutEffect is a React hook that is very similar to useEffect, but it runs synchronously immediately after render and before DOM repaint. This means it executes at the moment when React has already updated the DOM, but hasn't yet "displayed" it to the user.
This means you can safely measure DOM dimensions, scroll position, or prepare layout animations without visible flickering to the user.
| useEffect | useLayoutEffect |
|---|---|
| Asynchronous: runs after render when DOM is already visible. | Synchronous: runs immediately after render but before repaint. |
| Suitable for side effects (fetch, event, subscription). | Suitable for layout effects (DOM measurement, scroll, animation). |
| Doesn't block UI rendering. | Can block rendering if there are heavy computations. |
When you want to know an element's height or width, the right time is useLayoutEffect. If you do the same with useEffect, it might calculate the already visible DOM, causing flickering.
import React, { useRef, useLayoutEffect, useState } from 'react';
export default function BoxMeasure() {
const boxRef = useRef();
const [boxHeight, setBoxHeight] = useState(0);
useLayoutEffect(() => {
const height = boxRef.current.getBoundingClientRect().height;
setBoxHeight(height);
}, []);
return (
<div>
<div
ref={boxRef}
style={{ width: '200px', height: '150px', background: '#61dafb', marginBottom: '10px' }}
></div>
<p>Block height: {boxHeight}px</p>
</div>
);
}
Here `useLayoutEffect` allows correctly measuring DOM dimensions before React paints it to the screen. If you did the same with `useEffect`, you would first see "0px" and then the update.
If you want to restore scroll position immediately after render, you should use `useLayoutEffect` because scrolling should occur before repaint.
import React, { useLayoutEffect } from 'react';
export default function ScrollTop() {
useLayoutEffect(() => {
window.scrollTo(0, 0);
}, []);
return (
<div style={{ height: '200vh', background: 'linear-gradient(#fff, #ccc)' }}>
<h2>Page scrolls to top immediately after render</h2>
</div>
);
}
With this example, the page scrolls immediately after render without visible movement.
When you need to reposition multiple DOM elements or create animations, you can use useLayoutEffect to measure previous and current positions.
import React, { useState, useLayoutEffect, useRef } from 'react';
export default function AnimatedBox() {
const [toggle, setToggle] = useState(false);
const boxRef = useRef();
useLayoutEffect(() => {
const box = boxRef.current;
box.style.transition = 'none';
box.style.transform = 'scale(0.5)';
requestAnimationFrame(() => {
box.style.transition = 'transform 0.4s ease';
box.style.transform = 'scale(1)';
});
}, [toggle]);
return (
<div>
<button onClick={() => setToggle(t => !t)}>Change Position</button>
<div
ref={boxRef}
style={{
marginTop: '20px',
width: '100px',
height: '100px',
background: toggle ? '#4caf50' : '#ff5722',
}}
></div>
</div>
);
}
This example uses `useLayoutEffect` to ensure smooth animation without "flicker". If you did the same with `useEffect`, React would render the transitional state.
For SSR, use:
if (typeof window !== 'undefined') { useLayoutEffect(...); }
Write a component that: