Hello! Welcome to the next lesson in our journey with the useEffect hook.
In the last lesson, we focused on how to run a side effect exactly once when a component first appears on the screen, or "mounts," by using an empty dependency array []. This is perfect for initial setup tasks like fetching data.
However, a component's life isn't just about mounting; it also gets removed, or "unmounts." What happens to the side effects we started? If a component starts a timer, subscribes to a data stream, or adds an event listener, those processes will keep running in the background even after the component is gone. This can lead to bugs, performance issues, and a common problem known as a memory leak.
Today, you will learn how to write a cleanup function within useEffect. This function is React's built-in mechanism for stopping side effects and releasing resources, ensuring your components are tidy and your application remains healthy.
1. The Problem: Lingering Side Effects and Memory Leaks
First, let's see the problem in action. A "memory leak" in React often happens when you try to update the state of a component that has already been unmounted. The component is gone, but some lingering process (like a completed API call or a timer) tries to talk to it, leading to a warning and wasted resources.
A setInterval function is a classic example of a side effect that needs cleaning up. Let's watch a video that demonstrates what happens when we start an interval in a component and then unmount that component without cleaning up.
USEEFFECT CLEANUP FUNCTION Simplified - How, and why you should use it
The video 'USEEFFECT CLEANUP FUNCTION Simplified' from the Deeecode channel clearly illustrates the problem. We'll watch the part where a setInterval is created in a child component. Pay attention to the console after the component is unmounted.
Watch from 00:22 to 01:47. You'll see a 'Child' component that starts a counter. Notice how the counter keeps running in the console even after the child component is removed from the screen.
As you saw, even though the component disappeared from the UI, the interval continued to run in the background. If this were a more resource-intensive task, running it unnecessarily would degrade your app's performance. If you were to toggle the component on and off multiple times, you would have multiple intervals running simultaneously, which is even worse!
This is a memory leak: the application is holding onto and executing a process that is no longer needed.
2. The Solution: The useEffect Cleanup Function
To solve this, useEffect allows you to return a function. This returned function is the cleanup function. React will automatically execute this function right before the component is unmounted.
The syntax is straightforward:
useEffect(() => {
// Your side effect logic goes here.
// For example, setting up a timer or subscription.
console.log('Effect has run!');
// Return a function. This is the cleanup function.
return () => {
// Your cleanup logic goes here.
// For example, clearing a timer or unsubscribing.
console.log('Cleaning up the effect!');
};
}, []); // Dependency array
Now, let's see how to apply this to the setInterval problem from the video. The setInterval function returns an ID that you can use with clearInterval to stop the timer.
USEEFFECT CLEANUP FUNCTION Simplified - How, and why you should use it
Let's continue with the same Deeecode video. It will now show you how to return a cleanup function from useEffect to properly stop the interval.
Watch from 01:47 to 02:25, and then from 02:42 to 03:09. The video explains how returning a function works and then implements clearInterval to fix the memory leak.
By returning a function that calls clearInterval, the timer is now properly stopped the moment the component is unmounted. The memory leak is fixed.
3. Another Common Use Case: Event Listeners
Cleaning up is not just for timers. It's crucial for any kind of subscription, especially DOM event listeners. If you add an event listener when a component mounts but forget to remove it on unmount, the listener will persist and might try to update a non-existent component.
This next video demonstrates this exact scenario with a mousemove event listener and shows the explicit warning React gives you.
React Hooks Tutorial - 10 - useEffect with cleanup
The video 'React Hooks Tutorial - 10 - useEffect with cleanup' from Codevolution provides another excellent example. It sets up a component that listens for mouse movements and then toggles its visibility.
Watch from 00:21 to 04:56. First, you'll see the problem: an error appears in the console when you move the mouse after the component has been unmounted. Then, you'll see the solution: using removeEventListener in the cleanup function.
The key takeaway is that for every addEventListener, you should have a corresponding removeEventListener in your cleanup function. This is a fundamental rule for writing robust React components.
4. When Does the Cleanup Function Run?
So far, we've focused on cleanup during unmounting. This is the most common and intuitive case. However, the cleanup function actually runs in one more scenario.
The cleanup function runs:
- Before the component unmounts: To perform final cleanup.
- Before the effect runs again: If your
useEffecthas dependencies, and one of them changes, React will run the cleanup function from the previous render before executing the effect for the current render. This prevents old, stale effects (like a subscription to a previous user ID) from continuing to run.
This flowchart provides a great visual summary of the entire useEffect lifecycle.

5. A Practical Checklist for Cleanup
You should get into the habit of asking, "Does this side effect need to be cleaned up?" whenever you write a useEffect. Here is a quick reference for common asynchronous operations and their corresponding cleanup methods.
| Async Operation | Setup Method | Cleanup Method |
|---|---|---|
| Timers | setTimeout() |
clearTimeout() |
| Intervals | setInterval() |
clearInterval() |
| DOM Events | addEventListener() |
removeEventListener() |
| Fetch API Calls | fetch() |
AbortController.abort() |
| WebSockets | new WebSocket() |
socket.close() |
| Library Subscriptions | library.subscribe() |
subscription.unsubscribe() |
For a deeper dive into the code for each of these scenarios, the following article is an excellent resource.
How to Fix 'Memory Leak' Warnings in useEffect
The article 'How to Fix 'Memory Leak' Warnings in useEffect' by OneUptime provides clear 'BAD' vs 'GOOD' code examples for many cleanup scenarios, including fetch with AbortController, timers, and event listeners.
Read the section 'Common Causes and Fixes' to see code examples for each type of cleanup. Then, review the 'Summary' table at the end, which provides a concise overview.
Conclusion
You've now learned a critical part of managing side effects in React. Writing proper cleanup functions ensures your application is efficient, bug-free, and doesn't waste system resources.
Key Takeaways:
- To clean up a side effect, you return a function from within
useEffect. - React automatically executes this cleanup function when the component unmounts or before the effect re-runs due to a dependency change.
- Failing to clean up side effects like timers, subscriptions, or event listeners can lead to memory leaks and bugs.
- Common cleanup operations include
clearInterval,clearTimeout,removeEventListener, andAbortController.abort().
Next Up:
We have now explored the three main phases of a component's life through the lens of useEffect: mounting (run once), updating (dependencies), and unmounting (cleanup). In the next lesson, we will synthesize all this knowledge to formally relate useEffect's behavior to these classic component lifecycle phases, giving you a complete mental model of how components live and die in React.