Skip to content

Fix 3 per-row parameterized-selector cache misses in TokenListItem (fresh object-literal args) #31492

@MajorLift

Description

@MajorLift

Performance audit finding · Severity: Medium · Effort: Medium · Fix risk: Simple · Test safety net: Partial
Owner: @MetaMask/metamask-assets (suggested)
File: app/components/UI/Tokens/TokenList/TokenListItem/TokenListItem.tsx

What is this about?

Per-selector triage of the 12 useSelector reads in TokenListItem: 9 ruled out (memoized selectors returning primitives/booleans or stable controller-state refs). The remaining 3 are per-row parameterized selectors whose caches bust on every call:

  1. selectAsset(state, { address, chainId, isStaked }) (TokenListItem.tsx:179) — called with a fresh object literal argument per render per row, which defeats any memoizer: a single-entry cache misses on the changed arg, and weakMapMemoize misses because every object literal is a new WeakMap key.
  2. selectNativeCurrencyByChainId(state, chainId) (TokenListItem.tsx:224) — varying chainId across rows busts a single-entry cache row-by-row.
  3. selectIsStakeableToken(state, asset) (TokenListItem.tsx:490) — takes the asset object as the argument; identity churns whenever selectAsset recomputes (which is every render, per item 1), compounding the miss rate.

Why it matters

The token list is the primary power-user surface; the misses multiply by visible rows on every render, and selectAsset recomputing per row hands each row a fresh asset ref, defeating the row's React.memo.

Scenario

N/A — see Technical Details.

Design

N/A — internal performance change; no UI/design impact.

Technical Details

Fix

For (1): stabilize the argument — memoize the { address, chainId, isStaked } key object per row (useMemo on the three primitives), or re-key the selector on the three primitives directly; pair with weakMapMemoize or a per-row factory instance. For (2): per-row factory instance via useMemo, or a chainId-keyed lookup-map selector. For (3): key on asset.address/asset.chainId primitives instead of the object. The codebase already has both correct tools in use: weakMapMemoize (selectNetworkConfigurationByChainId) and useMemo-instantiated factory selectors (AccountGroupBalance.tsx:91-98).

Threat Modeling Framework

N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.

Acceptance Criteria

  • The 3 selectors hit their caches across renders with unchanged data (assert result toBe across two calls; recomputation count ~0 while scrolling with stable data).
  • Profiler: rows with unchanged data do not re-render during a balance-poll flush.
  • Reassure perf-test on the token list locks the win in CI.

References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    To be fixed

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions