Three React useEffect Cleanups You're Probably Skipping — and the Memory Leaks They Cause
These leaks survive code review and automated tests because they don't produce errors — they just degrade performance over hours of uptime. A single-page app with unmounted-but-uncleaned chart instances, lingering WebSocket connections, and stale window listeners can leak tens of megabytes per user session, directly hitting Core Web Vitals and user retention on long-running dashboards and data-heavy SPAs.
Heap snapshot comparisons in Chrome DevTools reveal that three common omissions in useEffect cleanup functions are responsible for steady memory growth in long-running React pages. Window event listeners left unremoved keep closures and component instances alive indefinitely. WebSocket and EventSource connections opened on dependency changes accumulate active connections that continue receiving data and triggering callbacks on unmounted components. Visualization library instances from ECharts, Chart.js, or map libraries hold onto Canvas contexts and internal state trees that React never frees.
The pattern is uniform: any registration operation — addEventListener, new WebSocket, library.init — demands a corresponding deregistration in the cleanup function. Without it, each mount cycle adds objects that garbage collection can never reach. The leaks are invisible during local development and pass code review silently, only manifesting as degraded performance after hours of real-world use.
The fix is mechanical. Return a function from useEffect that calls removeEventListener, close, dispose, or unsubscribe. The mechanism has existed since React's earliest versions; the failure is not technical but habitual — developers simply don't register the need for cleanup at write time.
The three leak patterns share an identical root cause — a registration without a deregistration — yet developers treat them as separate problems because the APIs look different. Recognizing the abstract pattern eliminates entire categories of leaks at design time rather than debugging them one by one.
Memory leaks from third-party visualization libraries are disproportionately expensive because each instance holds Canvas buffers and off-screen rendering caches that are orders of magnitude larger than typical JavaScript objects. A single undisposed chart instance can cost more memory than a hundred lingering event listeners.
The React team built the useEffect cleanup mechanism from day one, which means the tooling for prevention has existed longer than the problem. The gap is entirely in developer habit formation, not framework capability.