DeployU
Interviews / Frontend Engineering / This component flickers when resizing. Fix it using the correct effect hook.

Questions

This component flickers when resizing. Fix it using the correct effect hook.

practical Hooks & Patterns Interactive Quiz Code Examples

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.

  1. Explain the difference between useEffect and useLayoutEffect in terms of their execution timing.
  2. Fix the component to eliminate the visual flicker, ensuring the div always renders as a perfect square from the very first paint.
Your Code
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>
  );
}
Click "Run Code" to see output...
Click "Run Code" to see test results...

The Explanation: useEffect vs. useLayoutEffect Timing

Wrong Approach

[object Object]

Right Approach

[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:

    1. React renders the SquareBox with its initial height (which is 0 or 'auto').
    2. The browser paints this initial, potentially incorrect, state.
    3. useEffect runs, measures the width, and calls setHeight(width).
    4. setHeight triggers another render.
    5. React renders SquareBox with the correct height.
    6. The browser paints the corrected state. This sequence causes the flicker.
  • 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?