Hint
No array = every render; [] = mount only; [dep] = when dep changes; cleanup = before next effect + unmount
useEffect(() => {
// effect logic
return () => { /* cleanup */ };
}, [dependencies]);
Three dependency modes:
// 1. No array — runs after EVERY render
useEffect(() => { document.title = 'Re-ran'; });
// 2. Empty array — runs ONCE after mount
useEffect(() => {
const ws = new WebSocket(url);
return () => ws.close(); // cleanup on unmount
}, []);
// 3. With deps — runs when any dep changes
useEffect(() => {
fetchUser(userId); // re-fetches whenever userId changes
}, [userId]);
Cleanup timing:
useEffect(() => {
const id = setInterval(() => tick(), 1000);
return () => clearInterval(id); // cleans up before next effect
}, [tick]);
exhaustive-deps catches these. If adding a dep causes an infinite loop, you likely need useCallback or to restructure the effect.