-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Stop Onyx from holding every selector forever #86765
Description
Original proposal from @MrMuzyk.
Background:
react-native-onyx's useOnyx hook accepts an optional selector function. The OnyxSnapshotCache class maps each selector to a unique integer ID via selectorIDMap for cache key generation. selectorIDMap is typed as WeakMap in the source but initialized as new Map() in the constructor.
Many selectors are module-scoped with a stable reference and only get added once. However, components that define selectors inside the function body or use factory functions create a new function object per render. Each of these functions is a closure that captures its component scope — often including transaction data and report objects. In heap profiling (Chrome DevTools, production build), each open/close of an expense entry adds ~240 KB to the heap that is never reclaimed.
Problem: When a user navigates the web app over an extended session, the Onyx selector cache accumulates new entries on each navigation that are never released, causing unbounded heap growth that degrades browser performance and can force the user to reload.
Solution:
Change the constructor of OnyxSnapshotCache in lib/OnyxSnapshotCache.ts (line 36) from this.selectorIDMap = new Map() to this.selectorIDMap = new WeakMap(), aligning the runtime initialization with the already-declared type annotation.
A WeakMap holds weak references to its keys — when a selector function is no longer referenced by any live component, the GC reclaims both the function and its closure-captured data. With this change, selectors from unmounted components are no longer retained, stopping the unbounded growth described above. WeakMap is a drop-in replacement here: selectorIDMap only uses .has(), .get(), and .set(), all of which WeakMap supports.
- Before: Opening/Closing expense was adding ~240kb each time to heap to remain there forever
- After: Opening/Closing expense ends up with GC cleaning up correctly so nothing is added to heap that would remain there forever
Before:
After:
Prod example:
Issue Owner
Current Issue Owner: @MrMuzykMetadata
Metadata
Labels
Type
Fields
Give feedbackProjects
Status