React useEffect Cleanup & Subscriptions
A cleanup function is used to remove resources when a component unmounts or before the effect runs again.
React useEffect Cleanup & Subscriptions
A cleanup function is used to remove resources when a component unmounts or before the effect runs again.
This helps prevent:
- Memory leaks
- Unnecessary API calls
- Multiple event listeners
- Multiple timers running simultaneously
Syntax
useEffect(() => {
// Setup code
return () => {
// Cleanup code
};
}, []);
Flow
Component Mount
↓
useEffect Runs
↓
Setup Timer / Listener / Subscription
↓
Component Unmount
↓
Cleanup Function Runs
1. Cleanup Timer Example
Problem
Without cleanup, the timer continues running even after the component is removed.
import { useEffect } from "react";
function Timer() {
useEffect(() => {
const interval = setInterval(() => {
console.log("Running...");
}, 1000);
return () => {
clearInterval(interval);
console.log("Timer Stopped");
};
}, []);
return <h2>Timer Component</h2>;
}
export default Timer;
Output
Running...
Running...
Running...
(Component Unmount)
Timer Stopped
2. Event Listener Cleanup
Without Cleanup
Every render may add another listener.
Correct Way
import { useEffect } from "react";
function WindowResize() {
useEffect(() => {
const handleResize = () => {
console.log(window.innerWidth);
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return <h2>Resize Window</h2>;
}
export default WindowResize;
Why?
When component unmounts:
Listener Removed
No extra listeners remain in memory.
3. Subscription Cleanup
Imagine subscribing to a chat service.
import { useEffect } from "react";
function Chat() {
useEffect(() => {
console.log("Subscribed");
return () => {
console.log("Unsubscribed");
};
}, []);
return <h2>Chat Room</h2>;
}
Output
Subscribed
(Component Unmount)
Unsubscribed
In real applications this could be:
- WebSocket connections
- Firebase listeners
- Chat subscriptions
- Notification services
Example: Toggle Component
App.jsx
import { useState } from "react";
import Timer from "./Timer";
function App() {
const [show, setShow] = useState(true);
return (
<div>
<button onClick={() => setShow(!show)}>
Toggle Timer
</button>
{show && <Timer />}
</div>
);
}
export default App;
Timer.jsx
import { useEffect } from "react";
function Timer() {
useEffect(() => {
const interval = setInterval(() => {
console.log("Tick...");
}, 1000);
return () => {
clearInterval(interval);
console.log("Cleanup Executed");
};
}, []);
return <h2>Timer Running</h2>;
}
export default Timer;
Result
Tick...
Tick...
Tick...
Click Toggle
Cleanup Executed
The timer stops immediately.
Cleanup Before Re-running Effect
Cleanup also runs before the effect executes again when dependencies change.
import { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Effect:", count);
return () => {
console.log("Cleanup:", count);
};
}, [count]);
return (
<>
<h2>{count}</h2>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</>
);
}
export default Counter;
Output
Effect: 0
Click
Cleanup: 0
Effect: 1
Click
Cleanup: 1
Effect: 2
Common Memory Leak Mistakes
❌ Forgetting to clear intervals
setInterval(() => {
console.log("Running");
}, 1000);
✅ Correct
const id = setInterval(() => {
console.log("Running");
}, 1000);
return () => clearInterval(id);
❌ Forgetting event listener cleanup
window.addEventListener("resize", handleResize);
✅ Correct
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
Interview Questions
What is a cleanup function?
A cleanup function is the function returned from useEffect. It runs when the component unmounts or before the effect runs again.
useEffect(() => {
return () => {
console.log("Cleanup");
};
}, []);
Why do we need cleanup functions?
To prevent:
- Memory leaks
- Unused timers
- Duplicate event listeners
- Open subscriptions
When does cleanup run?
- When the component unmounts.
- Before the effect runs again due to dependency changes.
Quick Cheat Sheet
SideEffectCleanup
setInterval()
clearInterval()
setTimeout()
clearTimeout()
addEventListener()
removeEventListener()
WebSocketsocket.close()
Subscriptionunsubscribe()
Build a timer component with start/stop and cleanup.
import Timer from "./Timer";
function App() {
return (
<div>
<h1>React Timer Demo</h1>
<Timer />
</div>
);
}
export default App;
import { useState, useEffect } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
const [isRunning, setIsRunning] = useState(false);
useEffect(() => {
let interval;
if (isRunning) {
interval = setInterval(() => {
setSeconds((prevSeconds) => prevSeconds + 1);
}, 1000);
}
// Cleanup Function
return () => {
clearInterval(interval);
console.log("Interval Cleared");
};
}, [isRunning]);
const startTimer = () => {
setIsRunning(true);
};
const stopTimer = () => {
setIsRunning(false);
};
const resetTimer = () => {
setIsRunning(false);
setSeconds(0);
};
return (
<div>
<h2>Time: {seconds} sec</h2>
<button onClick={startTimer}>
Start
</button>
<button onClick={stopTimer}>
Stop
</button>
<button onClick={resetTimer}>
Reset
</button>
</div>
);
}
export default Timer;

메타데이터
- post_id
- baebfa42647d
- slug
- react-useeffect-cleanup-subscriptions-baebfa42647d
- url
- https://medium.com/@gm962460/react-useeffect-cleanup-subscriptions-baebfa42647d
- canonical_url
- https://medium.com/@gm962460/react-useeffect-cleanup-subscriptions-baebfa42647d
- author_url
- https://medium.com/@gm962460
- status
- ok
- fetched_at
- 2026-06-20 20:29:01