What's the difference between useEffect and useLayoutEffect? When would you use each?
3 minintermediatereactdifferenceuseeffectuselayouteffect
Quick Answer
useEffect and useLayoutEffect are both hooks for handling side effects, but they differ in when they execute during the React rendering cycle.
Detailed Answer
What's the difference between useEffect and useLayoutEffect? When would you use each?
Answer:
useEffect and useLayoutEffect are both hooks for handling side effects, but they differ in when they execute during the React rendering cycle.
Key Differences:
-
Timing:
useEffect: Runs after the DOM has been updated and painted to the screen (asynchronous)useLayoutEffect: Runs synchronously after all DOM mutations but before the browser paints
-
Performance Impact:
useEffect: Non-blocking, doesn't delay visual updatesuseLayoutEffect: Blocking, can delay visual updates if it takes too long
When to Use useEffect (Most Common):
function DataFetcher({ userId }: { userId: string }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
// Good for data fetching, subscriptions, timers
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
// Good for cleanup
useEffect(() => {
const timer = setInterval(() => {
console.log('Timer tick');
}, 1000);
return () => clearInterval(timer);
}, []);
return loading ? <div>Loading...</div> : <div>{user?.name}</div>;
}
When to Use useLayoutEffect (Specific Cases):
function Tooltip({ children, content }: { children: React.ReactNode; content: string }) {
const [position, setPosition] = useState({ top: 0, left: 0 });
const tooltipRef = useRef<HTMLDivElement>(null);
const triggerRef = useRef<HTMLDivElement>(null);
// Use useLayoutEffect to prevent visual flicker
useLayoutEffect(() => {
if (tooltipRef.current && triggerRef.current) {
const triggerRect = triggerRef.current.getBoundingClientRect();
const tooltipRect = tooltipRef.current.getBoundingClientRect();
// Calculate position to prevent tooltip from going off-screen
const top = triggerRect.bottom + 8;
const left = Math.max(8, Math.min(
triggerRect.left,
window.innerWidth - tooltipRect.width - 8
));
setPosition({ top, left });
}
}, [content]);
return (
<div ref={triggerRef} style={{ position: 'relative' }}>
{children}
<div
ref={tooltipRef}
style={{
position: 'absolute',
top: position.top,
left: position.left,
background: 'black',
color: 'white',
padding: '4px 8px',
borderRadius: '4px',
zIndex: 1000,
}}
>
{content}
</div>
</div>
);
}
// Another example: Measuring DOM elements
function DynamicHeightComponent({ content }: { content: string }) {
const [height, setHeight] = useState(0);
const contentRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
if (contentRef.current) {
// Measure the actual rendered height
const measuredHeight = contentRef.current.scrollHeight;
setHeight(measuredHeight);
}
}, [content]);
return (
<div style={{ height: height || 'auto' }}>
<div ref={contentRef}>{content}</div>
</div>
);
}
Summary:
- Use
useEffectfor 99% of side effects (data fetching, subscriptions, cleanup) - Use
useLayoutEffectonly when you need to read layout from the DOM and synchronously re-render to prevent visual flicker - Common use cases for
useLayoutEffect: tooltips, modals, measuring elements, animations that depend on DOM measurements