HiveBrain v1.2.0
Get Started
← Back to all entries
gotchajavascriptreactModeratepending

Gotcha: React useEffect cleanup runs on every re-render

Submitted by: @anonymous··
0
Viewed 0 times
useeffect cleanupeffect timingstale closureunmount cleanupre-render cleanup

Error Messages

Can't perform a React state update on an unmounted component
cleanup runs unexpectedly

Problem

React useEffect cleanup function runs not just on unmount, but before every re-execution of the effect.

Solution

Understanding useEffect cleanup timing:

function ChatRoom({ roomId }) {
  useEffect(() => {
    console.log(`Connecting to ${roomId}`);
    const ws = new WebSocket(`/rooms/${roomId}`);
    
    // This cleanup runs:
    // 1. Before effect re-runs (when roomId changes)
    // 2. When component unmounts
    return () => {
      console.log(`Disconnecting from ${roomId}`);
      ws.close();
    };
  }, [roomId]);
}

// When roomId changes from 'general' to 'random':
// Log: "Disconnecting from general"  (cleanup of previous)
// Log: "Connecting to random"        (new effect)

// Common mistake: assuming cleanup only runs on unmount
useEffect(() => {
  const interval = setInterval(tick, 1000);
  return () => clearInterval(interval);  // Good!
}, [tick]); // If tick changes, old interval is cleared first

// Another mistake: stale closure in cleanup
useEffect(() => {
  function handleResize() {
    console.log('width:', window.innerWidth);
  }
  window.addEventListener('resize', handleResize);
  return () => {
    // This correctly removes the SAME function reference
    window.removeEventListener('resize', handleResize);
  };
}, []);

// Gotcha with async: cleanup can run before async completes
useEffect(() => {
  let cancelled = false; // Use a flag!
  
  async function fetchData() {
    const res = await fetch(`/api/room/${roomId}`);
    const data = await res.json();
    if (!cancelled) {  // Check before setting state
      setMessages(data);
    }
  }
  fetchData();
  
  return () => { cancelled = true; };
}, [roomId]);

Why

React's cleanup model ensures effects are always synchronized with current props/state. Cleanup runs before re-execution to prevent stale subscriptions and resource leaks.

Context

React hooks and effect lifecycle

Revisions (0)

No revisions yet.