MetaMask / MetaMask/metamask-mobile
Virtualize the installed-snaps list (ScrollView + .map) in SnapsSettingsList
- Dominant language
- TypeScript
- Stars
- 3k
- Forks
- 1.7k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 669
Description
> **Performance audit finding** · Severity: **Medium** · Effort: Easy · Fix risk: Simple · Test safety net: Uncovered
> Owner: `@MetaMask/core-platform`
> File: `app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx:40`
### What is this about?
`SnapsSettingsList` renders every installed Snap inside a plain `ScrollView` by
mapping over `Object.values(snaps)`. There is no virtualization, so all
`SnapElement` rows are mounted at once regardless of how many Snaps the user has
installed. For power users (or once more first-party Snaps ship) this mounts the
full list up front, paying the layout/mount cost for off-screen rows.
**Why it matters**
A `ScrollView` keeps every child mounted. Each `SnapElement` is a non-trivial
row (avatar/network image, text, navigation handler). As the installed-Snap
count grows, time-to-interactive for this settings screen scales linearly and
off-screen rows still consume memory and layout work. A `FlashList` would only
render the visible window.
### Scenario
N/A — see Technical Details.
### Design
N/A — internal performance change; no UI/design impact.
### Technical Details
**Evidence**
`app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx:38`
```tsx
return (
{(Object.values(snaps) as Snap[]).map((snap: Snap) => (
))}
);
```
`snaps` comes from `selectSnaps` (line 25), an unbounded controller-state map.
**Fix**
Replace the `ScrollView` + `.map()` with a `FlashList` (v2 — do not set
`estimatedItemSize`):
```tsx
const data = useMemo(() => Object.values(snaps) as Snap[], [snaps]);
return (
snap.id}
renderItem={({ item }) => }
/>
);
```
Keep `renderItem` and `keyExtractor` stable (module-scope or `useCallback`).
### Threat Modeling Framework
N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.
### Acceptance Criteria
- - Install many Snaps (or mock `selectSnaps` with 50+ entries) and confirm only
the visible rows mount (log in `SnapElement` or use React DevTools profiler).
- Confirm scrolling stays smooth and existing `SnapsSettingsList` behavior is
unchanged. No co-located unit test exists; add a render test asserting the
list renders provided Snaps.
### References
- File: `app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx:40`
- Source: MetaMask Mobile performance audit — finding `list-snaps-settings-scrollview-map`
- Owner (CODEOWNERS / best-effort): @MetaMask/core-platform
- Status: **UNVALIDATED**
Contributor guide
Research direction
Start with app/components/Views/Snaps/SnapsSettingsList/SnapsSettingsList.tsx, especially the selectSnaps usage and current ScrollView mapping. Replace the unvirtualized list as specified, then add a render test for provided Snaps and verify that many entries render only visible rows while existing behavior and scrolling remain unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react-native, typescript
- Domain
- mobile, performance
- Issue type
- Refactor
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100