perf(account-selector): cut list render churn and per-cell selector cost - #33877
Conversation
Two low-risk changes that reduce the AccountSelector ("Account List")
open cost, validated by React.Profiler on a Samsung Galaxy A22 with a
180-account / 45-group wallet:
- Memoize the `selectedAccountGroups` array passed to the list so a parent
re-render no longer breaks the list's memoization (rebuilding selectedIdSet
and renderItem, forcing full re-renders of the visible cells).
- Replace the per-cell `selectIconSeedAddressByAccountGroupId` deep-equal
selector factory with a single shared map selector
(`selectAllAccountGroupIconSeedAddresses`) read O(1) per cell, and move the
chainId-only `selectInternalAccountByAccountGroupAndScope` subscription into
a child rendered only when a chainId is provided (never on the account list).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR template — items to address before "Ready for review"Warnings — informational, address before merging:
See docs/readme/ready-for-review.md for the full Definition of Ready for Review. |
…n mock - Add unit tests for `selectAllAccountGroupIconSeedAddresses` (EVM preference, non-EVM fallback, multi-wallet map, empty/unresolved groups, missing tree). - Add the new selector to MultichainAccountsConnectedList's full module mock so AccountCell (which now reads it) no longer calls undefined. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
|
@metamaskbot app-profiling-check --all --run 30313222010 |
|
🔬 App profiling check started for run |
🔬 App Profiling Check: Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3Device: Google Pixel 8 Pro (v14.0) · Platform: Android Current: run 30313222010
Current profilingSummary{
"status": "success",
"issues": 2,
"criticalIssues": 1,
"appSizeMb": 318.21,
"cpu": {
"avg": 4.8,
"max": 22.18,
"unit": "%"
},
"memory": {
"avg": 682.78,
"max": 719.81,
"unit": "MB"
},
"battery": {
"total": 3.53,
"percentage": 2.291,
"unit": "mAh"
},
"diskIO": {
"reads": 0,
"writes": 29170,
"unit": "kb"
},
"networkIO": {
"upload": 3833.02,
"download": 10601.28,
"unit": "kb"
},
"uiRendering": {
"slowFrames": 17.71,
"frozenFrames": 0,
"anrs": 0
}
}🔬 App Profiling Check: Fresh SRP wallet creation performanceDevice: Google Pixel 8 Pro (v14.0) · Platform: Android Current: run 30313222010
Current profilingSummary{
"status": "success",
"issues": 3,
"criticalIssues": 3,
"appSizeMb": 318.21,
"cpu": {
"avg": 9.73,
"max": 22.72,
"unit": "%"
},
"memory": {
"avg": 567.94,
"max": 837.66,
"unit": "MB"
},
"battery": {
"total": 2.14,
"percentage": 1.949,
"unit": "mAh"
},
"diskIO": {
"reads": 0,
"writes": 33740,
"unit": "kb"
},
"networkIO": {
"upload": 1552.21,
"download": 4280.1,
"unit": "kb"
},
"uiRendering": {
"slowFrames": 43.88,
"frozenFrames": 0,
"anrs": 0
}
} |
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection:
Why these tags:
The changes are refactors/optimizations rather than feature additions, but they touch critical rendering paths for account selection used across many flows. Risk is medium because the logic is preserved but the selector architecture changed. Performance Test Selection: |
⚡ Performance Test Results
❌ 1 test failed · 2 tests · 1 device 📱 Devices tested (1)Android: Google Pixel 8 Pro (v14.0) ❌ Failed Tests (1)
Compare all failed scenarios
✅ Passed Tests (1)
Branch: |
vinnyhoward
left a comment
There was a problem hiding this comment.
Oh wow nice improvement. LGTM!



Description
The
AccountSelectorshould render within the navigation animation so switching accounts feels instant. Profiling a power-user wallet (45 account groups) on a Samsung Galaxy A22 showed the open was dominated by the per-cell render pipeline, not by data/balance computation. Two low-risk fixes target that:selectedAccountGroupsarray passed to the list. Previously a new[selectedAccountGroup]literal was allocated on every parent render, breakingReact.memoon the list and rebuilding itsselectedIdSet/renderItem— forcing full re-renders of the visible cells across FlashList's fill passes. A stable reference lets those passes do far less redundant work.selectIconSeedAddressByAccountGroupIddeep-equal selector factory, so N cells each deep-compared the full account tree state on every store update. This is replaced with one shared map selector (selectAllAccountGroupIconSeedAddresses) readO(1)per cell. ThechainId-onlyselectInternalAccountByAccountGroupAndScopesubscription is moved into a child rendered only when achainIdis provided, so it never runs on the account list (which passes nochainId).Measured deltas (dev build + React.Profiler, so absolute wall-clock is inflated — the relative deltas are the signal; Samsung Galaxy A22, 45 account groups):
The array memoization (fix 1) contributes almost all of the win; the selector change (fix 2) is a smaller, targeted reduction plus removes an unused per-cell subscription. No behavior change.
Changelog
CHANGELOG entry: N/A
Related issues
Refs: https://consensyssoftware.atlassian.net/browse/MUL-2010
Manual testing steps
Screenshots/Recordings
N/A
Pre-merge author checklist
Performance checks (if applicable)
React.Profiler; theShowAccountListSentry span already exists. Production trace instrumentation not added here.Pre-merge reviewer checklist
🤖 Generated with Claude Code
Note
Low Risk
Render-performance refactor with parity tests; no auth, payments, or data-model changes beyond selector wiring.
Overview
Improves account picker open time for wallets with many account groups by reducing unnecessary list and cell re-renders, with no intended UI or behavior change.
AccountSelector now passes a memoized
selectedAccountGroupsarray intoMultichainAccountSelectorListinstead of a fresh[selectedAccountGroup]on every parent render, so memoized list props (selectedIdSet,renderItem) stay stable.AccountCell reads avatar icon seed addresses from a new shared Redux selector
selectAllAccountGroupIconSeedAddresses(one map build per store update, O(1) lookup per row) instead of each row creating its own deep-equalselectIconSeedAddressByAccountGroupIdfactory. Network-specific address text and theselectInternalAccountByAccountGroupAndScopesubscription move intoAccountNetworkAddressRow, which only mounts whenchainIdis set—so the main account list avoids that subscription entirely. Network badge images are derived fromchainIdwithout an extra store read.Selector unit tests cover the new map selector; connected-list tests mock it alongside existing account selector mocks.
Reviewed by Cursor Bugbot for commit 2181ddc. Bugbot is set up for automated code reviews on this repo. Configure here.