Skip to content
matthewcm.dev

    ↑ ↓ move · ↵ open · tab scopeopen graph →

    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.

    Growingtended planted 2 min read
    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 const unions.

    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/catch doesn’t catch a rejected promise unless you await it.

    Related: AG Grid Performance Patterns · Observability

    • #react
    • #performance
    • #memory-leaks
    • #profiling
    • #re-renders

    ↪ Links to (4)