🧹 Cleanup Function in useEffect
A cleanup function is used to remove or stop side effects created inside
useEffect.It helps prevent memory leaks and unwanted behavior.
The cleanup function is returned from the
useEffectcallback.
🔄 When Does Cleanup Run?
Before the component unmounts
Before the effect runs again (when dependencies change)
✍️ Syntax
useEffect(() => {
// setup side effect
return () => {
// cleanup logic
};
}, [dependencies]);✅ Common Use Cases
Clearing timers (
setInterval,setTimeout)Removing event listeners
Cancelling API subscriptions
Stopping observers or sockets
🧪 Simple Example
<div class="infographic-ueinterval" id="ui">
<style>
.infographic-ueinterval {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--line: #ddd6c9;
--accent: #2563eb;
--radius: 8px;
--mono-font: 'IBM Plex Mono', monospace;
--body-font: 'Inter', system-ui, sans-serif;
max-width: 700px;
margin: 0 auto;
box-sizing: border-box;
background: transparent;
color: var(--ink);
font-family: var(--body-font);
}
.infographic-ueinterval[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--line: #2c313d;
--accent: #5b8def;
}
.infographic-ueinterval * { box-sizing: border-box; min-width: 0; }
.ui-grid { display: flex; flex-direction: column; gap: 1.1rem; align-items: stretch; }
.ui-code-block pre {
margin: 0; background: #0d0d10; border: 1px solid #24252d; border-radius: var(--radius);
padding: 0.9rem; overflow: auto; max-height: 320px;
}
.ui-code-block code { font-family: var(--mono-font); font-size: 0.78rem; line-height: 1.75; color: #d4d4d8; white-space: pre; }
.tok-kw { color: #c678dd; }
.tok-punc { color: #6b7280; }
.tok-str { color: #98c379; }
.tok-expr { color: #56b6c2; }
.ui-output-label {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.1em; text-transform: uppercase;
color: var(--ink-soft); text-align: center;
}
.ui-console {
border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; box-shadow: 0 10px 24px -12px rgba(26,26,26,0.18);
background: #0d0d10;
}
.ui-console-bar { background: #1a1a1a; padding: 0.45rem 0.7rem; display: flex; align-items: center; justify-content: space-between; }
.ui-console-dots { display: flex; gap: 0.3rem; }
.ui-console-dots span { width: 8px; height: 8px; border-radius: 50%; display: block; }
.ui-console-dots span:nth-child(1) { background: #ff5f56; }
.ui-console-dots span:nth-child(2) { background: #ffbd2e; }
.ui-console-dots span:nth-child(3) { background: #27c93f; }
.ui-console-title { font-family: var(--mono-font); font-size: 0.68rem; color: #7f848e; }
.ui-console-body { padding: 0.7rem 0.9rem; min-height: 130px; max-height: 220px; overflow-y: auto; }
.ui-line { font-family: var(--mono-font); font-size: 0.78rem; color: #98c379; line-height: 1.8; opacity: 0; animation: ui-fade 0.3s ease forwards; }
@keyframes ui-fade { from { opacity: 0; transform: translateY(-2px); } to { opacity: 1; transform: translateY(0); } }
.ui-line.ui-stop { color: #e06c75; font-style: italic; }
.ui-line.ui-start { color: #61afef; font-style: italic; }
.ui-btn-row { display: flex; justify-content: center; gap: 0.6rem; padding: 0.7rem 0; border-top: 1px solid #1e1e1e; }
.ui-mount-btn, .ui-unmount-btn {
font-family: var(--body-font); font-size: 0.82rem; font-weight: 600; color: #fff;
border: none; border-radius: 100px; padding: 0.5rem 1.1rem; cursor: pointer; transition: transform 0.15s, opacity 0.15s;
}
.ui-mount-btn { background: #16a34a; }
.ui-unmount-btn { background: #dc2626; }
.ui-mount-btn:hover:not(:disabled), .ui-unmount-btn:hover:not(:disabled) { transform: translateY(-1px); }
.ui-mount-btn:disabled, .ui-unmount-btn:disabled { opacity: 0.45; cursor: default; transform: none; }
@media (prefers-reduced-motion: reduce) {
.infographic-ueinterval *, .infographic-ueinterval *::before, .infographic-ueinterval *::after {
transition-duration: 0.001ms !important; animation-duration: 0.001ms !important;
}
}
</style>
<div class="ui-grid">
<div class="ui-code-block">
<pre><code id="uiCode"></code></pre>
</div>
<p class="ui-output-label">OUTPUT</p>
<div class="ui-console">
<div class="ui-console-bar">
<span class="ui-console-dots"><span></span><span></span><span></span></span>
<span class="ui-console-title">console</span>
</div>
<div class="ui-console-body" id="uiConsoleBody"></div>
<div class="ui-btn-row">
<button class="ui-mount-btn" id="uiMountBtn" type="button">▶ Run (mount component)</button>
<button class="ui-unmount-btn" id="uiUnmountBtn" type="button" disabled>Unmount component</button>
</div>
</div>
</div>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('ui');
if (!root) return;
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
window.addEventListener('message', function (e) {
if (e.data && e.data.type === 'darkMode') {
if (e.data.value) root.setAttribute('data-theme', 'dark');
else root.removeAttribute('data-theme');
}
});
var code =
'<span class="tok-kw">useEffect</span><span class="tok-punc">(() => {</span>\n' +
' <span class="tok-kw">const</span> timer <span class="tok-punc">=</span> setInterval<span class="tok-punc">(() => {</span>\n' +
' console.log<span class="tok-punc">(</span><span class="tok-str">"Running..."</span><span class="tok-punc">);</span>\n' +
' <span class="tok-punc">},</span> <span class="tok-expr">1000</span><span class="tok-punc">);</span>\n\n' +
' <span class="tok-kw">return</span> <span class="tok-punc">() => {</span>\n' +
' clearInterval<span class="tok-punc">(</span>timer<span class="tok-punc">);</span>\n' +
' <span class="tok-punc">};</span>\n' +
'<span class="tok-punc">},</span> <span class="tok-punc">[]);</span>';
root.querySelector('#uiCode').innerHTML = code;
var consoleBody = root.querySelector('#uiConsoleBody');
var mountBtn = root.querySelector('#uiMountBtn');
var unmountBtn = root.querySelector('#uiUnmountBtn');
var timer = null;
function log(text, cls) {
var div = document.createElement('div');
div.className = 'ui-line' + (cls ? ' ' + cls : '');
div.textContent = text;
consoleBody.appendChild(div);
consoleBody.scrollTop = consoleBody.scrollHeight;
}
// nothing runs until the button is clicked — mirrors useEffect only
// firing once the component actually mounts, not on page load
mountBtn.addEventListener('click', function () {
if (timer) return;
log('// effect ran: setInterval(timer, 1000) — component mounted', 'ui-start');
timer = setInterval(function () {
log('Running...');
}, 1000);
mountBtn.disabled = true;
mountBtn.textContent = 'Mounted';
unmountBtn.disabled = false;
});
unmountBtn.addEventListener('click', function () {
clearInterval(timer); // real cleanup — same clearInterval(timer) as the code's return function
timer = null;
log('// cleanup ran: clearInterval(timer) — component unmounted', 'ui-stop');
unmountBtn.disabled = true;
unmountBtn.textContent = 'Unmounted';
});
})();
</script>🧠 Explanation:
Timer starts when component mounts
Timer stops when component unmounts
Prevents background execution
🔁 Class Component Equivalent
Class Component | useEffect |
|---|---|
| Cleanup function |
⭐ Key Points
Cleanup is optional, but important
Always clean up timers, listeners, subscriptions
One
useEffect= one cleanup function
🔚 In Short
Cleanup function = undo what your effect did