π§Ή React useEffect
Cleanup Techniques β Prevent Memory Leaks (2025 Guide)
π§² Introduction β Why Cleanup Matters in useEffect
In React.js, side effects such as timers, subscriptions, event listeners, and animations must be cleaned up properly to avoid memory leaks and unexpected behavior. The useEffect
hook provides a built-in way to clean up side effects using a return function inside the effect.
π― In this guide, youβll learn:
- How to perform cleanup inside
useEffect
- When cleanup is triggered
- Common patterns for event listeners, intervals, and API calls
- Best practices to keep your effects safe and efficient
π§© What Is useEffect Cleanup?
Inside useEffect
, you can return a function. This cleanup function is invoked:
- When the component unmounts
- Before the effect re-runs (if dependencies change)
β Syntax:
useEffect(() => {
// effect logic
return () => {
// cleanup logic here
};
}, [dependencies]);
π 1. Cleanup on Component Unmount
β Example β Clear Interval:
useEffect(() => {
const timer = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timer); // cleanup
console.log('Timer cleared');
};
}, []);
β
Cleans up only once, when component unmounts
π Essential for timers, sockets, polling
π 2. Cleanup Before Dependency Change
β Example β Remove Event Listener:
useEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // or [dependencies]
β Avoids multiple listeners being attached during re-renders
π¦ 3. Cleanup for Subscriptions or External Connections
β Example β WebSocket:
useEffect(() => {
const socket = new WebSocket('ws://localhost:1234');
socket.onmessage = (e) => console.log('Message:', e.data);
return () => {
socket.close(); // cleanup connection
};
}, []);
β Always close subscriptions or open connections
π 4. Canceling In-flight Async Tasks
You can’t await
directly in useEffect
, but you can use flags or AbortController
.
β Example β Using a flag:
useEffect(() => {
let isMounted = true;
fetch('/api/data')
.then((res) => res.json())
.then((data) => {
if (isMounted) {
setData(data);
}
});
return () => {
isMounted = false;
};
}, []);
β
Example β With AbortController
:
useEffect(() => {
const controller = new AbortController();
fetch('/api/data', { signal: controller.signal })
.then(res => res.json())
.then(setData)
.catch((err) => {
if (err.name !== 'AbortError') throw err;
});
return () => controller.abort();
}, []);
β Preferred for canceling real fetch requests cleanly
π§ 5. Why useEffect Cleanup Prevents Bugs
Without Cleanup | With Cleanup |
---|---|
Multiple intervals keep running | Old intervals are cleared |
Event listeners pile up | Only one listener active |
Memory usage grows | Freed on unmount |
State updates on unmounted components | Prevented via cleanup flags or abort |
π Best Practices
β
Always clean up timers, listeners, subscriptions
β
Use AbortController
for fetch-based effects
β
Use useRef
to track values across re-renders
β
Keep cleanup logic paired with effect logic
β
Separate unrelated effects into multiple useEffect
hooks
π Summary β Recap & Next Steps
The cleanup function inside useEffect
helps you avoid leaks, duplicates, and crashes. Itβs crucial for timers, subscriptions, and async requests. Ignoring cleanup can lead to hard-to-track bugs in real-world apps.
π Key Takeaways:
useEffect
cleanup runs on unmount or before re-run- Use it to remove intervals, listeners, or cancel requests
- Always pair effect logic with cleanup logic
- Use
AbortController
for safe async cleanup - Clean effects = more stable and optimized apps
βοΈ Real-World Relevance:
Used in production apps for live updates, real-time chats, search debouncing, and component teardown in apps like Slack, Zoom, and Google Docs.
β FAQ Section
β When is the useEffect
cleanup function called?
β
It runs:
- On component unmount
- Before the effect re-executes (when dependencies change)
β How do I cancel a fetch request in useEffect
?
β
Use AbortController
:
const controller = new AbortController();
fetch(url, { signal: controller.signal });
return () => controller.abort();
β Can I clean up multiple things in one effect?
β
Yes. Just return a single function that handles all the cleanup tasks.
β Can I return async () => {...}
from useEffect
?
β No. useEffect
must return a synchronous cleanup function. Use async IIFE inside the effect instead.
β Is cleanup needed for useEffect(() => {...}, [])
?
β
Yes, if you’re using timers, subscriptions, or side effects that persist beyond render.
Share Now :