Finding React Re-render and Memory Leaks
When a long-running React screen gets slower over time, measure heap, DOM nodes and listeners, let the shape of the growth point to the cause, then fix context-driven re-renders, unstable keys and subscriptions that remount.
On this page
Trading screens stay open all day, so problems that never show up in a quick test (a few extra listeners, a component that remounts) compound into a screen that’s sluggish by mid-afternoon. This is the checklist I’ve built up.
Measure first
Open the browser’s performance monitor and watch CPU, JS heap, DOM nodes and event listeners over a few minutes of real use. The shape of the growth tells you where to look:
- Heap stable, DOM nodes growing: components or columns being recreated, often from unstable keys.
- Listeners growing: subscriptions not being cleaned up, or everything wired up once at app start and never released.
- Heap growing: take memory snapshots and look for detached DOM elements still referenced from JavaScript.
A render-tracking tool such as why-did-you-render shows which re-renders you didn’t expect.
Common causes
- Context for hot state. Every consumer re-renders on each change, and a re-render high in the tree can even remount children, dropping their WebSocket subscriptions and creating duplicates. Use selector-based stores for state that changes often and keep Context for dependency injection (Zustand Transient Updates).
key={index}in dynamic lists: subtle render and layout bugs.- An un-memoised user or auth object at the top of the tree re-renders the whole app.
- Loading everything in the app shell. Every resource and subscription wired up at the root slows the first load and leaves listeners active on pages that don’t need them.
- Inline style objects, and TypeScript enums that aren’t erasable and add bundle weight; prefer classes and
as constunions.
Fix patterns
- Throttle streaming state updates (around 100 ms is plenty for a human); debounce with a leading edge for controls that should feel instant but not spam.
- Keep state close to where it’s used; refs are for references, not global state.
- One owner per socket, with components subscribing to slices (Snapshot and Delta Sync over WebSockets).
- Suspend API polling when the user leaves the page or the tab is hidden.
- Remember that
try/catchdoesn’t catch a rejected promise unless youawaitit.
Related: AG Grid Performance Patterns · Observability
- #react
- #performance
- #memory-leaks
- #profiling
- #re-renders
↩ Linked from (4)
- AG Grid Performance PatternsGrowing
Related: Snapshot and Delta Sync over WebSockets · Finding React Re-render and Memory Leaks · Zustand Transient Updates
- Migrating from CRA to ViteGrowing
Related: Finding React Re-render and Memory Leaks · Web Accelerator
Related: WebSockets · Server-Sent Events (SSEs) · Finding React Re-render and Memory Leaks
…tree could even remount children and duplicate their socket subscriptions. The investigation is written up in Finding React Re-render and Memory Leaks.
↪ Links to (4)
Related by topic
- Growingcase study
Building a Graph Component Library
Frontend craft - Growing
Graph Visualisation Libraries
Real-time UIs - Growing
Rust and WebAssembly: Benchmark Before You Bet
Frontend craft - Growingcase study
Smart TV Apps on Low-End Devices
Frontend craft