MetaMask / MetaMask/metamask-mobile
Replace inline permitted-accounts selector + isEqual in DaimoPayModal
Nobody has claimed this yet.
- 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 (app/components/UI/Card/components/DaimoPayModal/DaimoPayModal.test.tsx exists; unclear it asserts selector stability)
Owner:@MetaMask/card
File:app/components/UI/Card/components/DaimoPayModal/DaimoPayModal.tsx:92
What is this about?
DaimoPayModal reads permitted accounts via an inline useSelector callback that calls getPermittedEvmAddressesByHostname(...) and is stabilized with isEqual. The inline selector derives a new array every invocation (it computes the permitted-address list on the fly), so without a memoized selector each Redux dispatch recomputes the list and the isEqual comparator deep-compares the result to suppress re-renders.
Why it matters
This pattern runs the derivation plus a deep isEqual on every store dispatch while the modal is open. getPermittedEvmAddressesByHostname is not memoized per-hostname here, so the work is repeated and isEqual is a band-aid covering the missing parametrized reselect selector. It also recomputes daimoOrigin (a new URL(...)) inline each render, feeding an unstable arg into the selector.
Scenario
N/A — see Technical Details.
Design
N/A — internal performance change; no UI/design impact.
Technical Details
Evidence
app/components/UI/Card/components/DaimoPayModal/DaimoPayModal.tsx:92
const permittedAccountsList = useSelector((state: RootState) => {
const permissionsControllerState = selectPermissionControllerState(state);
return getPermittedEvmAddressesByHostname(
permissionsControllerState,
daimoOrigin,
);
}, isEqual);
daimoOrigin is computed inline just above (new URL(webViewUrl).origin).
Fix
Create a memoized parametrized selector, e.g. selectPermittedEvmAddressesByHostname(state, hostname) built with createSelector (using weakMapMemoize/argsMemoize so different hostnames cache independently). Memoize daimoOrigin with useMemo([payId]), then useSelector(state => selectPermittedEvmAddressesByHostname(state, daimoOrigin)) without isEqual. The selector returns a stable reference for unchanged inputs, eliminating the deep compare.
Threat Modeling Framework
N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.
Acceptance Criteria
-
- Run
yarn jest app/components/UI/Card/components/DaimoPayModal/DaimoPayModal.test.tsx.
- Run
- Add a selector test asserting a stable reference for the same hostname across unrelated state changes.
References
- File:
app/components/UI/Card/components/DaimoPayModal/DaimoPayModal.tsx:92 - Source: MetaMask Mobile performance audit — finding
redux-daimopay-isequal-inline-selector - Owner (CODEOWNERS / best-effort): @MetaMask/card
- Status: UNVALIDATED
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with app/components/UI/Card/components/DaimoPayModal/DaimoPayModal.tsx around line 92 and review the existing DaimoPayModal.test.tsx coverage. Run yarn jest app/components/UI/Card/components/DaimoPayModal/DaimoPayModal.test.tsx before changing the selector. Done means the parametrized selector and memoized daimoOrigin avoid isEqual, and a selector test confirms stable references across unrelated state changes.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- performance, testing-qa
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100