Cleanup Functions in useEffect
In this page:
Returning a Cleanup Function
If the function passed to useEffect returns another function, React treats that returned function as cleanup logic. React calls it right before running the effect again, and one final time when the component is removed from the page.
Note: Cleanup functions are especially important for anything that keeps running in the background, like setInterval or a WebSocket connection.
Warning: Forgetting the cleanup function for a setInterval effect means the interval keeps running even after the component is gone, wasting resources.
Example: Returning a Cleanup Function
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function Timer() {
const [seconds, setSeconds] = React.useState(0);
React.useEffect(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id);
}, []);
return <p>Seconds: {seconds}</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<Timer />);
</script>
</body>
</html>
Cleanup Runs Before Re-running the Effect
Whenever an effect's dependencies change and it's about to run again, React first calls the previous cleanup function. This ensures old subscriptions or listeners are properly removed before new ones are set up, preventing duplicates from piling up.
Note: Think of cleanup as always pairing with setup -- if the effect subscribes to something, the cleanup should unsubscribe from that same thing.
Warning: Without cleanup, an effect that adds an event listener on every dependency change will keep stacking up duplicate listeners.
Example: Cleanup Runs Before Re-running the Effect
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function KeyLogger() {
const [key, setKey] = React.useState("");
React.useEffect(() => {
function handleKey(e) { setKey(e.key); }
window.addEventListener("keydown", handleKey);
return () => window.removeEventListener("keydown", handleKey);
}, []);
return <p>Last key pressed: {key || "(none yet)"}</p>;
}
ReactDOM.createRoot(document.getElementById('root')).render(<KeyLogger />);
</script>
</body>
</html>
Cleanup on Unmount
When a component is removed from the page entirely, React calls its effect's cleanup function one final time. This is the standard place to release any resources the component was using, so nothing keeps running after the component is gone.
Note: If your component sets something up that should stop existing once the component disappears, that logic belongs in the cleanup function.
Warning: A component that toggles on and off repeatedly (via conditional rendering) mounts and unmounts each time, running setup and cleanup every time too.
Example: Cleanup on Unmount
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function Blinker() {
React.useEffect(() => {
console.log("Blinker mounted");
return () => console.log("Blinker unmounted -- cleanup ran");
}, []);
return <p>I blink on and off (see console on toggle).</p>;
}
function App() {
const [show, setShow] = React.useState(true);
return (
<div>
<button onClick={() => setShow(!show)}>Toggle</button>
{show && <Blinker />}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>
- Forgetting to return a cleanup function for effects that set up subscriptions, timers, or event listeners, causing memory leaks.
- Returning something other than a function (like a value or nothing when a function was needed) from the effect.
- Assuming cleanup only runs when the component unmounts -- it also runs before every re-run of the effect itself.
- A cleanup function is returned from inside the effect function.
- React calls it before the effect runs again, and when the component unmounts.
- Cleanup is essential for timers, subscriptions, and event listeners to avoid leaks.
- Not every effect needs a cleanup function -- only ones that set up something ongoing.
No browser-specific restrictions -- cleanup behavior is consistent since Hooks' introduction in React 16.8.
Chapter Quiz — Complete all 12 topics to unlock
0/12 topics done
Complete these topics first: