MetaMask / MetaMask/metamask-mobile

Memoize SendContext provider value to stop re-rendering all 26 consumers

Open Beginner friendly
#31,265 1 comment 0 reactions 0 assignees View on GitHub
area-performance Sev2 size-S ta-triaged team-confirmations
Dominant language
TypeScript
Stars
3k
Forks
1.7k
Avg merge
1d 14h
Merged PRs (30d)
669

Description

> **Performance audit finding** · Severity: **High** · Effort: Easy · Fix risk: Simple · Test safety net: Partial
> Owner (CODEOWNERS): `@MetaMask/confirmations`
> File: `app/components/Views/confirmations/context/send-context/send-context.tsx:108`

### What is this about?

`SendContextProvider` passes a brand-new inline object literal to `SendContext.Provider value={{ ... }}` on every render. Because the object identity changes each render, every component that calls `useSendContext()` re-renders whenever the provider re-renders, even if the specific field it reads did not change.

`useSendContext` has **26 consumer files** across the Send flow (amount input, recipient input, asset pickers, review screens, etc.). The provider holds several `useState` values (`asset`, `to`, `value`, `maxValueMode`, `fromAccount`) plus derived `chainId`. Typing in the amount field calls `setValue` on every keystroke, re-rendering the provider and therefore the entire Send subtree — not just the amount component. On the Send flow this is a per-keystroke fan-out.

### Scenario

```gherkin
GIVEN a user is on the Send flow
WHEN they type a digit in the amount field (setValue fires on every keystroke)
THEN the SendContext provider re-renders
AND all 26 useSendContext() consumers re-render — not only the amount component
```

### Design

N/A — internal performance refactor; no UI or design change.

### Technical Details

Current code — `app/components/Views/confirmations/context/send-context/send-context.tsx:107`:

```tsx
return (

{children}

);
```

`updateValue` and `handleUpdateAsset` are already `useCallback`-stabilized; `updateTo`/`setMaxValueMode` are stable setters. Only the wrapping object is unstable.

**Fix** — wrap the value in `useMemo` keyed on the actual fields:

```tsx
const value = useMemo(
() => ({
asset,
chainId,
fromAccount,
from: fromAccount?.address as string,
maxValueMode,
to,
updateAsset: handleUpdateAsset,
updateTo,
updateValue,
value,
}),
[asset, chainId, fromAccount, maxValueMode, to, handleUpdateAsset, updateValue],
);
return {children};
```

This does not eliminate re-renders when a field actually changes (consumers still re-render when `value` changes); it removes the spurious fan-out when the provider re-renders for unrelated reasons.

### Threat Modeling Framework

N/A — performance-only change. No new data flow, trust boundary, or attack surface; behavior is preserved (memoization only changes referential identity of the context value).

### Acceptance Criteria

- The `SendContext.Provider` value is memoized so its identity is stable across provider re-renders that don't change any field.
- `yarn jest app/components/Views/confirmations/context/send-context/send-context.test.tsx` passes.
- A leaf consumer (e.g. recipient input) no longer re-renders when only the amount changes — verified with a render-count probe or the React DevTools Profiler.

### References

- File: `app/components/Views/confirmations/context/send-context/send-context.tsx:108`
- Source: MetaMask Mobile performance audit — finding `context-send-context-inline-value`
- Status: **UNVALIDATED** (validate the regression before/after with the steps above)

Contributor guide

Open the contributing guide

Research direction

Start with app/components/Views/confirmations/context/send-context/send-context.tsx around line 108 and inspect the existing provider value and hook callbacks. Apply the stated memoization design, then run app/components/Views/confirmations/context/send-context/send-context.test.tsx. Verify the provider value remains stable when its fields do not change, and use a render-count probe or React DevTools Profiler to check the recipient consumer behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
react-native, typescript
Domain
frontend, mobile
Issue type
Refactor
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.