Side Effects with useEffect
On this page
When useEffect Is the Right Tool
- Synchronizing with external systems: network, subscriptions, timers, DOM APIs.
- Not for computing values that can be derived during render.
- Not for orchestrating complex UI state machines without explicit modeling.
Dependency Arrays (The Production Rules)
- Dependencies must include everything read from the outer scope that affects the effect.
- Missing dependencies cause stale reads and non-deterministic behavior.
- Incorrect dependencies cause loops and excess work.
Cleanup Discipline
- Always cleanup subscriptions, intervals, and listeners.
- Cleanup runs before re-running the effect and on unmount.
Example: Timer with Cleanup
React.useEffect(() => {
const id = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(id);
}, []);
Async Effects: Race-Safe Pattern
- Requests can complete out of order; you must ignore stale results.
- Use an ignore flag or AbortController depending on your fetch stack.
Example: Ignore Stale Responses
React.useEffect(() => {
let ignore = false;
async function load() {
try {
const res = await fetch(`/api/users?q=${encodeURIComponent(query)}`);
const data = await res.json();
if (!ignore) setUsers(data);
} catch (e) {
if (!ignore) setError(String(e));
}
}
load();
return () => { ignore = true; };
}, [query]);
Failure Modes
- Infinite loops: effect sets state that changes a dependency on every run.
- Stale closures: missing deps cause effect to read old values.
- Memory leaks: missing cleanup for subscriptions/timers.
- Race conditions: late responses overwrite fresh state.
- Double fetch: repeated fetches triggered by unstable dependencies (inline objects/functions).
Incident Triage Checklist
- Is the dependency array correct and complete?
- Is there a cleanup for any long-lived resource?
- Does async logic ignore/cancel stale requests?
- Are dependencies stable (no inline object/function churn)?