MetaMask / MetaMask/metamask-mobile

Virtualize the Send asset selector token/NFT lists (ScrollView + .map)

Open
#31,315 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area-performance Sev2 ta-needs-engineer-escalation ta-triaged team-confirmations
Dominant language
TypeScript
Stars
3k
Forks
1.7k
Avg merge
1d 14h
Merged PRs (30d)
669

Description

> **Performance audit finding** · Severity: **Medium** · Effort: Hard · Fix risk: Risky · Test safety net: Partial
> Owner: `@MetaMask/confirmations`
> File: `app/components/Views/confirmations/components/send/asset/asset.tsx:229`

### What is this about?

The Send flow's asset selector renders the entire token and NFT lists inside a
single `ScrollView`. `TokenList` and `NftList` each `.map()` over growable
arrays of assets and mount a real row component for every visible item with no
virtualization or recycling. A "Show more" button grows the visible slice
(`TOKEN_COUNT_PER_PAGE = 20`, `NFT_COUNT_PER_PAGE = 5`) by appending more mounted
rows to the same ScrollView.

**Why it matters**

For a power user with many tokens across multiple chains (and NFTs), every
"Show more" tap permanently mounts another batch of fully-rendered rows in one
ScrollView — none are recycled or unmounted as they scroll off screen. This
grows the view hierarchy and memory linearly with the number of revealed assets,
increasing mount cost, scroll jank, and memory pressure. Each `Token` row also
does formatting/price work; ScrollView keeps them all alive simultaneously.
Keys are stable (`${chainId}-${address}`), so the immediate correctness is fine
— this is purely a virtualization gap. The repo standard is FlashList v2 for
growable lists; this list opts out of it.

### Scenario

N/A — see Technical Details.

### Design

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

### Technical Details

**Evidence**

`app/components/Views/confirmations/components/send/asset/asset.tsx:229`
```tsx

...

...

```

`app/components/Views/confirmations/components/token-list/token-list.tsx:89` (full map of growable tokens, no virtualization)
```tsx
{visibleTokens.map((token) => (

))}
```

`app/components/Views/confirmations/components/nft-list/nft-list.tsx:71` (same pattern for NFTs)
```tsx
{visibleNfts.map((nft) => (

))}
```

**Fix**

Replace the ScrollView + `.map()` paging with a single virtualized `FlashList`
that renders tokens and NFTs as sections (or two FlashLists), recycling rows as
the user scrolls. `keyExtractor` can reuse the existing stable keys, and
`onEndReached` can replace the manual "Show more" button. Because the section
header text and the highlighted-items block live in the same scroll container,
use `ListHeaderComponent` / a sectioned data array to preserve layout.

If a full migration is too large in one step, an interim improvement is to keep
the ScrollView but cap mounted rows and recycle off-screen ones — but FlashList
is the standard and the cleaner fix.

### Threat Modeling Framework

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

### Acceptance Criteria

- - Manual: in the Send flow with a wallet holding 100+ tokens/NFTs, scroll the
asset selector and profile JS thread + memory (Hermes sampling). Confirm
mounted row count stays bounded (recycled) rather than growing with scroll.
- Confirm selection still navigates correctly (`onSelect` / asset-selection
metrics fire with the right position) — these depend on `tokens.findIndex`,
which must keep operating on the full array, not the visible slice.
- Extend `token-list.test.tsx` / `nft-list.test.tsx` to assert rows render and
selection works after migration; add a scroll/recycle assertion if feasible.

### References

- File: `app/components/Views/confirmations/components/send/asset/asset.tsx:229`
- Source: MetaMask Mobile performance audit — finding `list-send-asset-selector-scrollview-map`
- Owner (CODEOWNERS / best-effort): @MetaMask/confirmations
- Status: **UNVALIDATED**

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with app/components/Views/confirmations/components/send/asset/asset.tsx, then read token-list.tsx and nft-list.tsx alongside token-list.test.tsx and nft-list.test.tsx. Run the existing list tests before changing the list rendering. Done means the asset selector recycles rows while preserving selection, metrics, layout, and correct behavior for expanded token and NFT lists.

Written by the indexing model from the issue text.

Assessment

Tech stack
react-native, typescript
Domain
frontend, mobile, performance
Issue type
Refactor
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.