While 99% of side effects should use useEffect (because it does not block the browser paint), there are specialized cases where an effect needs to read element dimensions and adjust styles before the user sees the screen. If done with useEffect, users will notice an annoying visual flicker or layout jump. The useLayoutEffect hook executes synchronously to prevent this flicker.
getBoundingClientRect()) and update state.import React, { useState, useRef, useLayoutEffect } from 'react';
function Tooltip({ targetElement, text }) {
const [tooltipHeight, setTooltipHeight] = useState(0);
const tooltipRef = useRef(null);
// Runs synchronously before the browser paints! Prevents visual reposition jump
useLayoutEffect(() => {
if (tooltipRef.current) {
const { height } = tooltipRef.current.getBoundingClientRect();
setTooltipHeight(height);
}
}, []);
return (
<div
ref={tooltipRef}
style={{
position: 'absolute',
top: -tooltipHeight - 10, // Places tooltip perfectly above button
background: '#0f172a',
color: '#fff',
padding: '6px 12px',
borderRadius: '4px',
fontSize: '12px'
}}
>
{text}
</div>
);
}
Now let us step into advanced parent-child imperative controls in React useImperativeHandle & forwardRef.
Accelerate your React mastery by studying these tightly connected core tutorials:
Asynchronous post-paint vs synchronous pre-paint.
Calculating element dimensions before flicker.
Where layout effects hook into the browser painting pipeline.