Navigate
Search topics, hooks, and fundamentals
What if an architect could measure the room before anyone walks in?
π Theme: Blueprint Architect
Your component is a building under construction. useLayoutEffect is the architect who measures the room and adjusts the blueprint before anyone walks in. Unlike the regular contractor (useEffect), the architect works synchronously between the build and the reveal β no flicker, no repositioning after the doors open.
With useEffect, the furniture renders at a default position first, then jumps to the correct spot after measurement β the client watches it teleport across the room.
Click "Place Furniture" β the sofa appears at the top-left corner, then teleports to its target!
useLayoutEffect measures the room synchronously before the browser paints β so the furniture appears in the right place immediately. The client never sees it move.
Click "Place Furniture" β the sofa appears at the correct position instantly. No teleporting!
useLayoutEffect blocks the browser paint β use it only when you need DOM measurements before the user sees the frame. For everything else, prefer useEffect.
function MasterArchitect() {
const [pos, setPos] = useState({ x: 0, y: 0 });
const roomRef = useRef(null);
// useLayoutEffect runs BEFORE paint β measure first!
useLayoutEffect(() => {
if (placed && roomRef.current) {
// Synchronous measurement β no delay, no flicker
setPos({ x: targetX, y: targetY });
}
}, [placed]);
// The sofa appears at the correct position on the
// very first frame β the client never sees it move.
// β οΈ useLayoutEffect blocks paint β use sparingly!
}useLayoutEffect is like an architect who measures the room before placing the furniture β not placing it, then shoving it around while the client watches.
useLayoutEffect is essential when you need DOM measurements before paint. Try these interactive blueprints.
Click any room to see its annotation. Toggle to useEffect and click different rooms β the annotation card flashes at the top-left corner before jumping to the right spot.
// useLayoutEffect β annotation positioned BEFORE paint
useLayoutEffect(() => {
if (!selected || !planRef.current) return;
const roomEl = planRef.current.querySelector(`[data-room="${selected}"]`);
const planRect = planRef.current.getBoundingClientRect();
const roomRect = roomEl.getBoundingClientRect();
setAnnotationPos({
top: roomRect.top - planRect.top,
left: roomRect.right - planRect.left + 8,
});
}, [selected]);
// useEffect β annotation flashes at (0,0) then jumps!
useEffect(() => {
// Same measurement, but runs AFTER paint
setTimeout(() => { /* ...measure & position... */ }, 50);
}, [selected]);Type a long name β with useEffect the beam snaps to minimum width first, then expands to fit the text.
// useLayoutEffect β measure & resize BEFORE paint
useLayoutEffect(() => {
if (!spanRef.current) return;
const rect = spanRef.current.getBoundingClientRect();
setWidth(rect.width + 32);
}, [text]);
// Beam resizes instantly β no flicker!
// useEffect β measure & resize AFTER paint
useEffect(() => {
// Reset to minimum width first (user sees the shrink)
setWidth(MIN_WIDTH);
setTimeout(() => {
const rect = spanRef.current.getBoundingClientRect();
setWidth(rect.width + 32);
}, 50);
}, [text]);
// Beam flashes at min width then expands β visible jump!These are the patterns that trip up developers most often. Switch between Wrong and Fixed to compare the code side by side.
function Dashboard() {
const [data, setData] = useState(null);
// Blocks paint for a network request β user sees a frozen screen
useLayoutEffect(() => {
fetch("/api/dashboard")
.then(res => res.json())
.then(d => setData(d));
}, []);
return <div>{data?.title}</div>;
}function Tooltip({ targetRef }) {
const [pos, setPos] = useState({ top: 0, left: 0 });
// useEffect runs AFTER paint β tooltip briefly appears at (0,0)
useEffect(() => {
const rect = targetRef.current.getBoundingClientRect();
setPos({ top: rect.bottom + 8, left: rect.left });
}, []);
return <div style={{ position: "absolute", ...pos }}>Tip</div>;
}import { useLayoutEffect, useState } from "react";
// Triggers a warning on the server: useLayoutEffect does nothing in SSR
function MeasuredBox({ children }) {
const [height, setHeight] = useState(0);
useLayoutEffect(() => {
// There is no DOM on the server β this is skipped entirely
}, []);
return <div>{children} (height: {height})</div>;
}