MetaMask / MetaMask/metamask-mobile
Memoize new Map result in selectRelatedChainIdsByTransactionId
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: Easy · Fix risk: Simple · Test safety net: Partial
> Owner: `@MetaMask/confirmations`
> File: `app/selectors/transactionController.ts:120`
### What is this about?
`selectRelatedChainIdsByTransactionId` is a plain `createSelector` whose result function returns a freshly-built `new Map(...)`. A Map is a new reference on every recompute, and two Maps with identical entries are never `===`. The result also nests `new Set` per transaction. The selector recomputes whenever the transactions array changes.
**Why it matters**
The transactions array changes on virtually every transaction lifecycle event. Each recompute returns a brand-new Map (and new inner arrays), breaking memoization for every consumer even when the related-chain mapping is unchanged.
### Scenario
N/A — see Technical Details.
### Design
N/A — internal performance change; no UI/design impact.
### Technical Details
**Evidence**
`app/selectors/transactionController.ts:120`
```ts
export const selectRelatedChainIdsByTransactionId = createSelector(
selectTransactionsStrict,
(transactions) => {
const transactionsById = new Map(
transactions.map((tx) => [tx.id, tx]),
);
return new Map(
transactions
.map((tx) => { /* ... */ return [tx.id, [...new Set(chainIds)]] ... })
.filter(([, chainIds]) => chainIds.length > 0),
);
},
);
```
**Fix**
Return a plain object (`Record`) so `createDeepEqualSelector` can deep-compare and keep the reference stable, and reconstruct a Map at the call site if a Map is required:
```ts
export const selectRelatedChainIdsByTransactionId = createDeepEqualSelector(
selectTransactionsStrict,
(transactions) => {
const result: Record = {};
// ...populate result[tx.id] = [...] ...
return result;
},
);
```
### Threat Modeling Framework
N/A — performance-only change; behavior is preserved, no new data flow / trust boundary / attack surface.
### Acceptance Criteria
- `app/selectors/transactionController.test.ts:197` asserts entries via `.get(...)` but not referential stability. Add a test asserting `toBe` across two equal-content transaction arrays. Profiler: confirm consumers stop re-rendering on unrelated transaction updates.
### References
- File: `app/selectors/transactionController.ts:120`
- Source: MetaMask Mobile performance audit — finding `selector-related-chain-ids-by-transaction-id`
- Owner (CODEOWNERS / best-effort): @MetaMask/confirmations
- 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 by reading app/selectors/transactionController.ts:120 and run the related tests in app/selectors/transactionController.test.ts:197. Inspect selector call sites to verify whether they require a Map, then update the selector and its test so equal-content transaction arrays preserve the same result reference without changing entries.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- mobile, performance, testing-qa
- Issue type
- Refactor
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 65/100