Cleanup Functions in useEffect
In this page:
useEffect(() => {
// set up: subscribe, start timer...
return () => {
// cleanup: unsubscribe, clear timer...
};
}, [dependency]);
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.
setInterval or a WebSocket connection.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.
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.
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: