patternjavascriptreactMajor
React useEffect cleanup function prevents memory leaks
Viewed 0 times
React 16.8+ for hooks
useEffect cleanupmemory leakunmountsubscriptionAbortControllersetState unmounted
browser
Error Messages
Problem
useEffect without a cleanup function causes memory leaks when the component unmounts — subscriptions stay active, timers keep running, and fetch responses try to update unmounted components.
Solution
Always return a cleanup function from useEffect:
useEffect(() => {
// Setup
const subscription = api.subscribe(data => setData(data));
const timer = setInterval(() => tick(), 1000);
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(data => setData(data))
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
// Cleanup — runs on unmount or before re-running effect
return () => {
subscription.unsubscribe();
clearInterval(timer);
controller.abort(); // cancels fetch
};
}, []); // dependency array
useEffect(() => {
// Setup
const subscription = api.subscribe(data => setData(data));
const timer = setInterval(() => tick(), 1000);
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(data => setData(data))
.catch(err => {
if (err.name !== 'AbortError') throw err;
});
// Cleanup — runs on unmount or before re-running effect
return () => {
subscription.unsubscribe();
clearInterval(timer);
controller.abort(); // cancels fetch
};
}, []); // dependency array
Why
React calls the cleanup function before the component unmounts AND before re-running the effect (when dependencies change). Without cleanup, subscriptions and timers persist after the component is gone, causing memory leaks and 'setState on unmounted component' warnings.
Gotchas
- Cleanup runs BEFORE each re-execution of the effect, not just on unmount
- AbortController is the modern way to cancel fetch requests
- The empty dependency array [] means effect runs once (mount) and cleanup runs once (unmount)
- In React 18 strict mode, effects run twice in development to catch missing cleanups
Code Snippets
Cancelable fetch in useEffect
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(r => r.json())
.then(setData)
.catch(e => { if (e.name !== 'AbortError') throw e; });
return () => controller.abort();
}, [url]);Context
When using useEffect with subscriptions, timers, or async operations
Revisions (0)
No revisions yet.