Common Causes and Fixes

Leaking Timer vs Fixed Timer

Example

Show both clocks, then hide them. The leaking clock keeps logging after it is gone; the fixed one stops because its effect returns a cleanup.

import { useState, useEffect } from 'react';

function LeakyClock() {
  useEffect(() => {
    setInterval(() => console.log('leaky clock still ticking...'), 1500); // no cleanup!
  }, []);
  return <p>Leaky clock</p>;
}

function FixedClock() {
  useEffect(() => {
    const id = setInterval(() => console.log('fixed clock tick'), 1500);
    return () => clearInterval(id);            // cleanup on unmount
  }, []);
  return <p>Fixed clock</p>;
}

export default function App() {
  const [show, setShow] = useState(false);
  return (
    <div>
      <button onClick={() => setShow(!show)}>{show ? 'Hide' : 'Show'} clocks</button>
      {show && <><LeakyClock /><FixedClock /></>}
    </div>
  );
}

Result

Causes and Fixes

CauseFix in the effect cleanup
setInterval / setTimeoutclearInterval(id) / clearTimeout(id)
window.addEventListenerwindow.removeEventListener with the same function
WebSocket / subscriptionssocket.close() / unsubscribe()
fetch still running after unmountAbortController.abort()
Global arrays/maps that keep growingremove entries, limit size
Closures holding big datado not keep large objects in long-lived callbacks

Event Listener Cleanup

useEffect(() => {
  const onResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', onResize);
  return () => window.removeEventListener('resize', onResize); // same function reference
}, []);