gotchajavascriptreactModeratepending
Gotcha: React useEffect cleanup runs on every re-render
Viewed 0 times
useeffect cleanupeffect timingstale closureunmount cleanupre-render cleanup
Error Messages
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.