Questions
This component flickers when resizing. Fix it using the correct effect hook.
The Scenario
A developer is building a component that needs to display a div that is always a perfect square (its height should always equal its width). They’ve implemented logic to measure the div’s width and then set its height using state, all within a useEffect hook.
However, they notice a brief visual flicker: the div first appears with its default height (or an incorrect height), then quickly resizes to become a square. This flicker is undesirable and creates a jarring user experience.
The Challenge
You’ve been given the SquareBox component.
- Explain the difference between
useEffectanduseLayoutEffectin terms of their execution timing. - Fix the component to eliminate the visual flicker, ensuring the
divalways renders as a perfect square from the very first paint.
import React, { useState, useEffect, useRef } from 'react';
export default function SquareBox() {
const boxRef = useRef(null);
const [height, setHeight] = useState(0);
useEffect(() => {
// THE BUG: useEffect runs AFTER the browser has painted.
// This causes a flicker as the div is first painted with default height,
// then measured, then re-rendered with the correct height.
if (boxRef.current) {
const width = boxRef.current.offsetWidth;
console.log(`useEffect: Measured width ${width}, setting height to ${width}`);
setHeight(width);
}
}, [height]); // Re-run when height changes (to re-measure if width changes)
return (
<div style={{ padding: '20px' }}>
<h1>Square Box</h1>
<div
ref={boxRef}
style={{
width: '200px', // Fixed width for demonstration
height: height || 'auto', // Initial height might be 'auto' or 0
backgroundColor: 'lightblue',
border: '2px solid blue',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '20px',
fontWeight: 'bold',
}}
>
I am a square!
</div>
<p>Resize your browser window to see the effect (if width changes).</p>
</div>
);
} The Explanation: useEffect vs. useLayoutEffect Timing
[object Object]
[object Object]
Understanding the Timing Difference
The visual flicker occurs because of the execution timing of useEffect.
-
useEffect: Runs asynchronously after React has performed all DOM mutations and after the browser has had a chance to paint the screen. This means:- React renders the
SquareBoxwith its initialheight(which is0or'auto'). - The browser paints this initial, potentially incorrect, state.
useEffectruns, measures thewidth, and callssetHeight(width).setHeighttriggers another render.- React renders
SquareBoxwith the correctheight. - The browser paints the corrected state. This sequence causes the flicker.
- React renders the
-
useLayoutEffect: Runs synchronously after React has performed all DOM mutations but before the browser has painted the screen.
The Fix: Using useLayoutEffect
For tasks that involve reading from the DOM (like measuring layout) and then immediately making synchronous DOM updates (like setting a style based on that measurement) that need to be visible before the user sees the next paint, useLayoutEffect is the correct hook to use. It ensures that these operations happen before the browser has a chance to paint the intermediate, incorrect state.
Here is the corrected implementation:
import React, { useState, useEffect, useLayoutEffect, useRef } from 'react';
export default function SquareBox() {
const boxRef = useRef(null);
const [height, setHeight] = useState(0);
// FIX: Use useLayoutEffect for DOM measurements that need to happen
// before the browser paints, to prevent flicker.
useLayoutEffect(() => {
if (boxRef.current) {
const width = boxRef.current.offsetWidth;
console.log(`useLayoutEffect: Measured width ${width}, setting height to ${width}`);
// Only update if necessary to avoid infinite loops
if (height !== width) {
setHeight(width);
}
}
}, [height]); // Re-run when height changes (to re-measure if width changes)
return (
<div style={{ padding: '20px' }}>
<h1>Square Box</h1>
<div
ref={boxRef}
style={{
width: '200px',
height: height || 'auto',
backgroundColor: 'lightblue',
border: '2px solid blue',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: '20px',
fontWeight: 'bold',
}}
>
I am a square!
</div>
<p>Resize your browser window to see the effect (if width changes).</p>
</div>
);
}
By using useLayoutEffect, the div’s height is measured and updated before the browser paints the screen, eliminating the visual flicker.
Practice Question
When does `useLayoutEffect` execute in the React component lifecycle?