Skip to main content

What does the cleanup function do in useEffect()?

It helps avoid memory leaks, duplicate subscriptions, unnecessary calls, and errors like

"Can't perform a React state update on an unmounted component".


Definition

A cleanup function is a function you return from useEffect, which React calls automatically:

  • before the component unmounts
  • or before this effect runs again (if its dependencies changed)

Syntax

javascript
useEffect(() => { // The effect body - runs on mount or update return () => { // Cleanup - runs on unmount or before a new effect run }; }, [dependencies]);

When exactly it's called

ScenarioWhen cleanup fires
The component unmountsOnce, before it's removed from the DOM
An effect dependency changedFirst the cleanup of the old effect, then a new run
The component re-rendered, but dependencies did not changeCleanup is not called

Example 1 - clearing a timer

javascript
useEffect(() => { const id = setInterval(() => { console.log('tick'); }, 1000); return () => { clearInterval(id); // cleanup on unmount console.log('cleaned up'); }; }, []); // [] → the effect fires once

What happens:

  • On mount, a timer is created.
  • On unmount (when the component is removed), cleanup stops the interval.

Example 2 - unsubscribing from events

javascript
useEffect(() => { const handleResize = () => console.log(window.innerWidth); window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); // cleanup }; }, []);

If you skip cleanup, the handler stays active even after the component is removed, causing a memory leak.


Example 3 - when dependencies change

javascript
useEffect(() => { console.log('Running effect for user:', userId); return () => { console.log('Cleaning up before the new effect for user:', userId); }; }, [userId]);

What happens:

  • When userId changes, React first calls the cleanup of the old effect, then runs the new effect.
  • This matters for cancelling old requests, closing connections, and so on.

Example 4 - cancelling asynchronous requests

javascript
useEffect(() => { const controller = new AbortController(); fetch(`/api/user/${userId}`, { signal: controller.signal }) .then(res => res.json()) .then(console.log) .catch(console.error); return () => { controller.abort(); // cancel the request if the component unmounts }; }, [userId]);

If the user quickly switches userId, the old request is cancelled, and the new effect starts from a clean state.


Why cleanup matters

Without cleanup functions, you can end up with:

  • Memory leaks (timers, subscriptions, WebSocket, listeners)

  • Duplicated effects on updates

  • Errors like:

    "Can't perform a state update on an unmounted component"


Analogy in class components

Function componentsClass components
return () => {...} inside useEffect()componentWillUnmount()
useEffect(() => {...}, [deps])componentDidUpdate() + componentWillUnmount() combined

Mini behavior table

ActionEffect runsCleanup runs
MountYesNo
Update with changed dependenciesYesYes (before it)
Update without changed dependenciesNoNo
UnmountNoYes

Summary

The cleanup function in useEffect() is a mechanism for cleaning up side effects, which React calls before the component is removed or before the effect runs again.

It:

  • frees up resources (timers, events, sockets),
  • prevents leaks,
  • guarantees the component always operates in a "clean" state.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.