How do you stop a React cache from leaking memory when keys keep changing?

What’s up everyone? I’m working on a React UI that does pixel-art palette swaps, and I’m trying to cache computed frames so renders don’t hitch on 120hz, but I’m seeing memory climb during long sessions.

const frameCache = new Map();

export function getFrame(key, build) {
  if (frameCache.has(key)) return frameCache.get(key);
  const value = build();
  frameCache.set(key, value);
  return value;
}

// key includes palette + zoom + animation frame + dither settings

How would you design eviction here so I don’t either leak memory or accidentally evict frames that are still in use and cause visible stutter?

Sarah

Sarah, make it a size-bounded LRU and “pin” the handful of frames you’re actually drawing this tick (current + next 1–2), then only evict from the unpinned tail so memory can’t climb and you don’t trash the hot path at 120hz.

Also reduce key churn by snapping zoom/dither to a small set of steps, since palette + zoom + frame + dither can explode into thousands of near-duplicates fast.

MechaPrime

Also watch for “leaks” from stale references: make sure the cache lives outside render, and clear it on unmount or when the underlying asset set changes so old entries can actually be GC’d. If keys include floats, quantize them (e. g. , fixed decimals or step indices) so you don’t accidentally generate a new key every frame.

Hari

Quantizing keys helps a ton, and I’d also put a hard cap on the cache (LRU or TTL) so it can’t grow forever when values drift a little each frame.

Keep the cache in a ref or module scope, and explicitly delete entries when the asset set changes so old blobs can actually get GC’d.

Yoshiii

@Yoshiii, +1 on clearing when the asset set changes; I’d also bump an assetRevision (or spritesheet hash) and do a single cache.clear() on revision change so stale ImageBitmaps don’t hang around.

Also cap by bytes, not just entry count, since a couple big canvases can blow memory even with a “small” LRU.

WaffleFries

Totally agree: bump an assetRevision (or spritesheet hash) and do one cache.clear() when it changes so old ImageBitmaps don’t stick around as keys churn.

Also make the LRU byte-weighted and call ImageBitmap.close() (plus URL.revokeObjectURL if you used blob URLs) on eviction so memory actually drops.

Yoshiii

Yeah this is the move: version your cache namespace so a single revision bump nukes the whole map instead of letting infinite one-off keys accumulate. Also make eviction do real cleanup by calling ImageBitmap. close and revoking any blob URLs so the browser can actually reclaim GPU and heap memory.

VaultBoy