BRUCE_FEBRUCE_FE

EN/CH Mode

BRUCE_FE React Interview Notes - Choosing between useEffect and useLayoutEffect

Deep comparison of useEffect and useLayoutEffect execution timing, use cases, and performance impact. Understand their differences in React rendering process, when to use useLayoutEffect to avoid flickering, and how to handle them properly in SSR environments.

影片縮圖

Lazy to read articles? Then watch videos!

Fundamental Differences in Execution Timing

The APIs of useEffect and useLayoutEffect are identical, but they differ in their execution timing within React's rendering process:

HookExecution TimingSync/AsyncScreen Update Timing
useEffectAfter render, after browser paintAsynchronousUser sees updates first, then effect executes
useLayoutEffectAfter render, before browser paintSynchronousUser sees updates after effect execution

Rendering Process Comparison:

useEffect Flow:

  1. 1. React updates Virtual DOM and calculates differences
  2. 2. React updates actual DOM
  3. 3. Browser paints screen (user sees updates)
  4. 4. Then executes useEffect callback

useLayoutEffect Flow:

  1. 1. React updates Virtual DOM and calculates differences
  2. 2. React updates actual DOM
  3. 3. Executes useLayoutEffect callback
  4. 4. Browser paints screen (user sees updates)
// Same basic syntax, but different execution timing
import { useEffect, useLayoutEffect } from 'react';

function ExampleComponent() {
  const [state, setState] = useState(initialState);
  
  // Executes after screen paint (non-blocking)
  useEffect(() => {
    // Code here runs after screen update
    // User sees UI even if operation takes time
  }, [dependencies]);
  
  // Executes before screen paint (blocking)
  useLayoutEffect(() => {
    // Code here runs after DOM update but before screen paint
    // Operations here block screen update until completion
  }, [dependencies]);
  
  return <div>...</div>;
}

Comparison of Use Cases

Simply put, the choice between useEffect and useLayoutEffect depends on when you need the side effects to execute:

HookUse CasesPractical Examples
useEffect
Recommended First Choice
  • 1. Data Fetching
  • 2. Event Listening
  • 3. Subscription Setup
  • 4. Timer Operations
  • 5. Logging
  • 1. API Requests for User Data
  • 2. Window Resize Monitoring
  • 3. WebSocket Connection Setup
  • 4. Periodic Online Status Updates
useLayoutEffect
For Specific Cases
  • 1. DOM Measurements
  • 2. Visual Animation Initialization
  • 3. Preventing Flicker
  • 4. Element Positioning
  • 1. Adjusting Layout After Height Measurement
  • 2. Precise Tooltip Positioning
  • 3. Scroll Position Adjustment
  • 4. Drag Component Initialization

Remember: Unless you need to perform DOM operations before screen painting (to avoid flicker or need precise measurements), you should use useEffect. useLayoutEffect blocks rendering and may affect performance.

Performance Impact

Choosing the appropriate hook can significantly impact application performance:

useEffect Performance Characteristics

  • 1. Non-blocking rendering - screen updates don't wait for effect completion
  • 2. Allows browser to paint UI before executing time-consuming operations
  • 3. More efficient for most side effects
  • 4. May cause visual flicker (if effect changes visible elements)

useLayoutEffect Performance Characteristics

  • 1. Blocking rendering - delays screen updates until effect completes
  • 2. Time-consuming operations in effect cause noticeable UI delays
  • 3. Prevents flickering issues
  • 4. More suitable for operations requiring synchronous DOM updates
// Performance impact case: flickering issue

// ❌ Using useEffect may cause flickering
function FlickeringExample() {
  const [width, setWidth] = useState(0);
  const divRef = useRef();
  
  useEffect(() => {
    // Issue: Initial state is rendered first, user sees this frame
    // Then measurement and update happen, causing content to jump
    setWidth(divRef.current?.getBoundingClientRect().width || 0);
  }, []);

  return <div ref={divRef} style={{ width: `${width}px` }}>Content may flicker</div>;
}

// ✅ Using useLayoutEffect prevents flickering
function SmoothExample() {
  const [width, setWidth] = useState(0);
  const divRef = useRef();
  
  useLayoutEffect(() => {
    // Advantage: Measurement and update complete before screen paint
    // User only sees final result, no intermediate states
    setWidth(divRef.current?.getBoundingClientRect().width || 0);
  }, []);

  return <div ref={divRef} style={{ width: `${width}px` }}>Content won't flicker</div>;
}

Practical Examples: When to Choose Each

Typical Examples of Using useLayoutEffect

// Example 1: Tooltip Positioning - Calculate position before screen paint
function Tooltip({ text, targetRef }) {
  const [position, setPosition] = useState({ top: 0, left: 0 });
  const tooltipRef = useRef(null);
  
  useLayoutEffect(() => {
    // Get dimensions of target element and tooltip
    // Calculate tooltip position (centered above target)
    // Update position state immediately, ensure completion before render
    if (targetRef.current && tooltipRef.current) {
      const targetRect = targetRef.current.getBoundingClientRect();
      const tooltipRect = tooltipRef.current.getBoundingClientRect();
      setPosition({
        top: targetRect.top - tooltipRect.height - 10,
        left: targetRect.left + targetRect.width / 2 - tooltipRect.width / 2
      });
    }
  }, [targetRef]);
  
  return <div ref={tooltipRef} style={{position: 'fixed', top: `${position.top}px`, left: `${position.left}px`}}>{text}</div>;
}

// Example 2: Scroll to Element - Prevent flickering
function ScrollToElement({ elementId }) {
  useLayoutEffect(() => {
    // Find target element and scroll immediately
    // Execute before screen paint, user won't see intermediate states
    const element = document.getElementById(elementId);
    if (element) element.scrollIntoView({ behavior: 'smooth' });
  }, [elementId]);
  
  return null;
}

Typical Examples of Using useEffect

// Example 1: Data Fetching
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    // Non-blocking operation, user can see loading state first
    setLoading(true);
    
    fetchUser(userId)
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(error => {
        console.error(error);
        setLoading(false);
      });
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;
  
  return <div>{user.name}</div>;
}

SSR (Server-Side Rendering) Special Considerations

In server-side rendering (SSR) environments, useLayoutEffect will show a warning and won't execute because there is no "layout phase" on the server. This is because:

  • 1. Server has no browser environment, cannot perform DOM measurements or updates
  • 2. The concept of 'browser painting' doesn't exist during SSR
  • 3. React on the server only renders HTML strings, no actual DOM manipulation

Handling useLayoutEffect in SSR environments:

// Method 1: Conditionally use different hooks
import { useEffect, useLayoutEffect } from 'react';

// Choose appropriate hook based on environment
const useIsomorphicLayoutEffect = 
  typeof window !== 'undefined' ? useLayoutEffect : useEffect;

function MyComponent() {
  useIsomorphicLayoutEffect(() => {
    // In browser, this behaves like useLayoutEffect
    // On server, this behaves like useEffect (actually doesn't execute)
    // ...
  }, []);
  
  return <div>...</div>;
}

// Method 2: Delay DOM operations until client-side
function SSRSafeComponent() {
  const [isMounted, setIsMounted] = useState(false);
  
  // Set as mounted on first client-side render
  useEffect(() => {
    setIsMounted(true);
  }, []);
  
  // Only execute DOM operations when client-side rendering
  useLayoutEffect(() => {
    // This effect only runs on client
    if (isMounted) {
      // Safely perform DOM measurements and operations...
    }
  }, [isMounted]);
  
  return <div>...</div>;
}

🔥 Common Interview Questions

(1) What are the main differences between useEffect and useLayoutEffect?

Answer: The main differences lie in their execution timing:

useEffect

• Asynchronous execution

• Executes after screen update

• Doesn't block rendering

useLayoutEffect

• Synchronous execution

• Executes before screen update

• Blocks rendering

Both have identical APIs but differ in execution timing, suitable for different scenarios.

(2) Why should useEffect be preferred in most cases?

Answer: Reasons to prefer useEffect:

  • 1. Better performance: doesn't block screen updates
  • 2. Suitable for most scenarios: data fetching, subscription setup, etc. don't need to block UI
  • 3. SSR friendly: more consistent behavior in server-side rendering environments
  • 4. Prevents lag: time-consuming operations don't delay UI display

(3) When should useLayoutEffect be used?

Answer: Scenarios suitable for useLayoutEffect:

Prevent Flickering

Element position or style needs adjustment before user sees it

DOM Measurement & Positioning

Tooltips, popups need precise positioning

Animation Initialization

Set correct initial state to avoid jumping

Scroll Position Control

Adjust scroll position before page display

// Simple example to prevent flickering
function Tooltip({ text, targetRef }) {
  const tooltipRef = useRef(null);
  
  useLayoutEffect(() => {
    // Measure target element position
    const targetRect = targetRef.current.getBoundingClientRect();
    // Position tooltip before screen update
    tooltipRef.current.style.top = targetRect.bottom + 'px';
    tooltipRef.current.style.left = targetRect.left + 'px';
  }, []);
  
  return <div ref={tooltipRef}>{text}</div>;
}

(4) How to safely use useLayoutEffect in SSR environments?

Answer: Server has no DOM, cannot execute layout effects. Solutions:

  1. 1. Create isomorphic hook, choose different hook based on environment
  2. 2. Use client-side detection, ensure execution only in browser
// Isomorphic hook solution
const useIsomorphicLayoutEffect = 
  typeof window !== 'undefined' 
    ? useLayoutEffect 
    : useEffect;

// Client-side detection solution
function SafeComponent() {
  const [isBrowser, setIsBrowser] = useState(false);
  
  useEffect(() => {
    setIsBrowser(true);
  }, []);
  
  useLayoutEffect(() => {
    if (isBrowser) {
      // Safely perform DOM operations
    }
  }, [isBrowser]);
  
  return <div>...</div>;
}

Best practice: Separate code requiring layout measurements into pure client-side components.