MetaMask / MetaMask/metamask-mobile

Memoize QRHardwareContext provider value and stabilize inline setters

Open Beginner friendly
#31,286 1 comment 0 reactions 0 assignees View on GitHub
area-performance Sev3 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: **Medium** · Effort: Easy · Fix risk: Simple · Test safety net: Partial
> Owner: `@MetaMask/confirmations`
> File: `app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.tsx:85`

### What is this about?

`QRHardwareContextProvider` passes an inline object literal to the provider, and two of its fields are inline arrow functions created fresh every render (`setRequestCompleted: () => setRequestCompleted(true)` and `setSigningConfirmed: () => setSigningConfirmed(true)`). Both the object and those function refs change on every render.

**Why it matters**

The provider re-renders whenever `useQRHardwareAwareness()` (a Redux selector on `state.qrKeyringScanner`) emits, plus on each local state change (`scannerVisible`, `signingConfirmed`, `isRequestCompleted`). With 6 consumer files in the QR-signing confirmation flow, every such update re-renders all consumers because the context value identity is unstable.

### Scenario

N/A — see Technical Details.

### Design

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

### Technical Details

**Evidence**

`app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.tsx:85`
```tsx
setRequestCompleted(true),
setScannerVisible,
setSigningConfirmed: () => setSigningConfirmed(true),
signingConfirmed,
}}
>
```
`cancelQRScanRequestIfPresent` is already `useCallback`-stabilized; the two inline setters and the object wrapper are not.

**Fix**

Promote the inline setters to `useCallback`-wrapped functions and wrap the value in `useMemo`:
```tsx
const markRequestCompleted = useCallback(() => setRequestCompleted(true), []);
const confirmSigning = useCallback(() => setSigningConfirmed(true), []);
const value = useMemo(
() => ({ pendingScanRequest, cameraError, cancelQRScanRequestIfPresent, isSigningQRObject,
needsCameraPermission: isSigningQRObject && !hasCameraPermission, scannerVisible,
setRequestCompleted: markRequestCompleted, setScannerVisible,
setSigningConfirmed: confirmSigning, signingConfirmed }),
[pendingScanRequest, cameraError, cancelQRScanRequestIfPresent, isSigningQRObject,
hasCameraPermission, scannerVisible, markRequestCompleted, confirmSigning, signingConfirmed],
);
```

### 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/Views/confirmations/context/qr-hardware-context/qr-hardware-context.test.tsx`.
- Profile the QR-signing flow and confirm consumers don't re-render on unrelated selector ticks.

### References

- File: `app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.tsx:85`
- Source: MetaMask Mobile performance audit — finding `context-qr-hardware-inline-value`
- Owner (CODEOWNERS / best-effort): @MetaMask/confirmations
- Status: **UNVALIDATED**

Contributor guide

Open the contributing guide

Research direction

Start with app/components/Views/confirmations/context/qr-hardware-context/qr-hardware-context.tsx at the provider around line 85, then read qr-hardware-context.test.tsx. Verify the provider callbacks and value are stable across unrelated updates without changing behavior. Run the specified Jest test and profile the QR-signing flow to confirm consumers avoid unnecessary re-renders.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.