- React
- Core React Hooks: State, Synchronization, and Refs
- useEffect Anti-Patterns & Re-render Traps
useEffect Anti-Patterns & Re-render Traps
Common useEffect traps include infinite re-render loops caused by updating state dependencies unconditionally, memory leaks from missing cleanups, and stale closures. In React 18 StrictMode, effects run mount -> cleanup -> mount to surface cleanup bugs.
Key Learning Objectives
Diagnose and resolve infinite re-render loops caused by cyclic state updates in useEffect.
Explain why development StrictMode mounts, cleans up, and re-mounts effects.
Identify anti-patterns like using effects for calculating derived state or handling user events.
Prevent object/function dependency reference cycles.
The Interview Problem & Context
What is logged to the console when simulating a component mounting under React 18 StrictMode with an effect that tracks mount count and cleans up properly?
1// Simulating React 18 StrictMode effect execution order2function simulateStrictModeEffect() {3 const logs: string[] = [];45 // Mount 16 logs.push('double-mount');78 // StrictMode immediately runs cleanup in dev!9 logs.push('clean');1011 // Mount 2 (Re-mount)12 logs.push('mount');1314 // Effect has run twice, but active subscription count is 115 const activeSubscriptions = 1;1617 return `${logs.join(' ')} ${activeSubscriptions}`;18}1920console.log(simulateStrictModeEffect());
Predict Console & Render Output
double-mount clean mount 1
mount clean 0
mount mount clean 2
mount clean clean 0
Execution Trace
Step 1 of 4 (Line 6)Mount 1 executes effect setup function.
Deep Architectural Explanation
The Most Pervasive useEffect Bugs
1. The Infinite Re-render Loop
useEffect(() => {
// ❌ BAD: Modifying state triggers a re-render.
// The re-render creates a new options object reference.
// The new object triggers useEffect again -> Infinite loop!
setData(prev => ({ ...prev, updated: true }));
}, [data]);Fix: Never update a state variable that is also listed in the effect's dependency array unless protected by an explicit exit condition (if (!data.updated)).
2. The StrictMode Double-Mount Behavior
In React 18, <React.StrictMode> deliberately does this on initial mount in development:
Component mountsEffect setup runsEffect cleanup runs(Simulating unmount)Effect setup runs again
Why? To prove whether your code survives being unmounted and re-mounted without breaking. If your cleanup function is missing or incomplete, timers will double up, websockets will open duplicate channels, and memory leaks will appear immediately in development!
Common Interview Traps & Pitfalls
Removing StrictMode because "my effects fire twice" instead of implementing proper cleanup functions.
Passing an inline object or function in the dependency array without useCallback/useMemo.
Triggering navigation (router.push) unconditionally inside useEffect.
Senior Interviewer Follow-Up Probes
How does React 18 Fast Refresh utilize StrictMode resilience?
Why does updating state in useEffect cause a render waterfall compared to updating it in event handlers?
How can you tell if an infinite loop is caused by useEffect or a bad state setter in the render body?
Frequently Asked Interview Questions
How do you fix an infinite loop in useEffect?
Remove unnecessary state dependencies, use functional state updaters (prev => next) so the state variable can be omitted from dependencies, or add an explicit guard condition.
Why does React 18 run effects twice in development mode?
StrictMode tests whether effects implement proper cleanup logic by mounting, unmounting, and re-mounting components to simulate future React features like tab backgrounding and Fast Refresh.
Can you call async functions directly inside useEffect?
No. useEffect must return either a cleanup function or undefined. You cannot pass an async function directly because async functions return a Promise.
How should you call an async function inside useEffect?
Declare the async function inside the effect and call it immediately: useEffect(() => { async function fetchData() { ... } fetchData(); }, []).
