useLayoutEffect is identical to useEffect, but it fires synchronously after all DOM mutations and before the browser has a chance to paint. Use this to read layout from the DOM and synchronously re-render.
Signature
function useLayoutEffect(
effect: () => void | (() => void),
inputs?: ReadonlyArray<unknown>
): void
Parameters
effect
() => void | (() => void)
required
A function containing imperative, possibly effectful code. Can optionally return a cleanup function that will be called before the effect runs again or when the component unmounts.
An array of dependencies. The effect will only re-run if one of the values in this array has changed (compared using ===). If omitted, the effect runs after every render.
Returns
void
Basic Usage
import { useLayoutEffect, useRef, useState } from 'preact/hooks';
function MeasureElement() {
const ref = useRef();
const [height, setHeight] = useState(0);
useLayoutEffect(() => {
// Measure DOM element synchronously before paint
if (ref.current) {
setHeight(ref.current.offsetHeight);
}
}, []);
return (
<div>
<div ref={ref}>Content to measure</div>
<p>Height: {height}px</p>
</div>
);
}
Preventing Flash of Incorrect Content
function Tooltip({ targetId, content }) {
const tooltipRef = useRef();
const [position, setPosition] = useState({ top: 0, left: 0 });
useLayoutEffect(() => {
const target = document.getElementById(targetId);
const tooltip = tooltipRef.current;
if (target && tooltip) {
const targetRect = target.getBoundingClientRect();
const tooltipRect = tooltip.getBoundingClientRect();
// Calculate position before paint to avoid flicker
setPosition({
top: targetRect.bottom + 8,
left: targetRect.left + (targetRect.width - tooltipRect.width) / 2
});
}
}, [targetId]);
return (
<div
ref={tooltipRef}
style={{
position: 'absolute',
top: position.top,
left: position.left
}}
>
{content}
</div>
);
}
Synchronizing with DOM Mutations
function ScrollToBottom({ messages }) {
const containerRef = useRef();
useLayoutEffect(() => {
// Scroll immediately after DOM update, before paint
if (containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
}, [messages]); // Re-run when messages change
return (
<div ref={containerRef} style={{ height: '400px', overflow: 'auto' }}>
{messages.map(msg => (
<div key={msg.id}>{msg.text}</div>
))}
</div>
);
}
Reading Layout Properties
function AdaptiveComponent() {
const ref = useRef();
const [isTooWide, setIsTooWide] = useState(false);
useLayoutEffect(() => {
if (ref.current) {
const width = ref.current.getBoundingClientRect().width;
setIsTooWide(width > 600);
}
});
return (
<div ref={ref} className={isTooWide ? 'compact' : 'expanded'}>
Content that adapts to its width
</div>
);
}
Focus Management
function AutoFocusInput({ shouldFocus }) {
const inputRef = useRef();
useLayoutEffect(() => {
if (shouldFocus && inputRef.current) {
// Focus before paint to avoid visible delay
inputRef.current.focus();
}
}, [shouldFocus]);
return <input ref={inputRef} type="text" />;
}
Measuring and Positioning
function DynamicDropdown({ isOpen, children }) {
const dropdownRef = useRef();
const [style, setStyle] = useState({});
useLayoutEffect(() => {
if (isOpen && dropdownRef.current) {
const rect = dropdownRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
// Determine if dropdown should open upward or downward
const shouldOpenUpward = rect.bottom + 200 > viewportHeight;
setStyle({
position: 'absolute',
[shouldOpenUpward ? 'bottom' : 'top']: '100%',
left: 0,
width: '100%'
});
}
}, [isOpen]);
return (
<div ref={dropdownRef}>
{isOpen && (
<div style={style} className="dropdown-menu">
{children}
</div>
)}
</div>
);
}
Performance Warning: useLayoutEffect runs synchronously and blocks the browser from painting. This can hurt performance if used excessively. Prefer useEffect when possible.
Use useLayoutEffect when you need to:
- Read layout from the DOM (measurements, positions)
- Mutate the DOM synchronously before paint
- Prevent visual flicker or “flash of incorrect content”
Updates scheduled inside useLayoutEffect will be flushed synchronously, after all DOM mutations but before the browser paints.