MetaMask / MetaMask/metamask-mobile
Stop eagerly rendering all network rows (initialNumToRender=999) in NetworkSelectorList
- 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: Uncovered
> Owner: `@MetaMask/mobile-core-ux (suggested)`
> File: `app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx:108`
### What is this about?
`NetworkSelectorList` uses a plain React Native `FlatList` with
`initialNumToRender={999}`, which disables windowing and renders up to 999
network rows synchronously on first mount. The same `onContentSizeChange` +
`scrollToOffset(yOffset)` auto-scroll workaround that drove the 999 in the
account selector is present here too. `networks` is growable — multichain users
with all popular networks plus custom RPC entries can have a long list.
**Why it matters**
This selector opens from network-switching entry points. Rendering every network
row up front (each a cell with avatar/badge + label) is an eager-work burst on
the JS thread at sheet-open time, hurting time-to-interactive and the open
animation, and disabling FlatList recycling thereafter. It is the same
anti-pattern as `CaipAccountSelectorList` and should be fixed the same way.
### Scenario
N/A — see Technical Details.
### Design
N/A — internal performance change; no UI/design impact.
### Technical Details
**Evidence**
`app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx:108`
```tsx
```
`app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx:88` (auto-scroll workaround the 999 supports)
```tsx
const onContentSizeChanged = useCallback(() => {
...
networkListRef?.current?.scrollToOffset({ offset: selectedNetwork?.yOffset ?? 0, animated: false });
```
**Fix**
Migrate to FlashList v2 (repo standard) and drop `initialNumToRender={999}`,
replacing the `yOffset` scroll hack with `initialScrollIndex` /
`scrollToIndex({ index, animated: false })` for the selected network. If
deferring migration, lower `initialNumToRender` to a realistic on-screen count
and use `scrollToIndex`/`getItemLayout` for the selected-network scroll.
### Threat Modeling Framework
N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.
### Acceptance Criteria
- - Add a test mounting the list with many networks and asserting the mounted row
count on first render is bounded (not equal to data length).
- Manual: with many networks configured, open the network selector and profile
the open interaction; confirm reduced mount work and that the selected network
still scrolls into view.
### References
- File: `app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx:108`
- Source: MetaMask Mobile performance audit — finding `list-network-selector-initialnumtorender-999`
- Owner (CODEOWNERS / best-effort): @MetaMask/mobile-core-ux (suggested)
- Status: **UNVALIDATED**
Contributor guide
Research direction
Start with app/components/UI/NetworkSelectorList/NetworkSelectorList.tsx, especially the FlatList at line 108 and the onContentSizeChange callback at line 88. Compare the related CaipAccountSelectorList implementation and inspect the repository’s FlashList v2 usage. Add coverage for bounded initial row mounting, then verify that a selected network still scrolls into view and profile the selector with many networks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- mobile, performance, testing-qa
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100