perf: Memoize ShieldSubscriptionProvider context value and callback#39309
perf: Memoize ShieldSubscriptionProvider context value and callback#39309
ShieldSubscriptionProvider context value and callback#39309Conversation
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
✨ Files requiring CODEOWNER review ✨🔐 @MetaMask/web3auth (2 files, +345 -31)
|
f6fdccb to
4f235d9
Compare
4f235d9 to
e6244fc
Compare
e6244fc to
aea7b5c
Compare
aea7b5c to
a7e31e8
Compare
Builds ready [a7e31e8]
UI Startup Metrics (1358 ± 89 ms)
📊 Page Load Benchmark ResultsCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs
|
a7e31e8 to
00f6e52
Compare
Builds ready [00f6e52]
UI Startup Metrics (1341 ± 109 ms)
Sum of mean exceeds: 0ms | Sum of p95 exceeds: 386ms Sum of all benchmark exceeds: 386ms 📊 Page Load Benchmark ResultsCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
ShieldSubscriptionProvider context value and callback
1e3027a to
a32aa94
Compare
- Remove non-existent properties from `useSubscriptionEligibility` mock - Cast `useSubscriptionMetrics` mock to match expected return type - Use mutable ref object for `evaluateFn` to fix TS control flow narrowing - Add `setPendingShieldCohort` to actions mock (from main)
9324916 to
f3adfee
Compare
Builds ready [f3adfee]
⚡ Performance Benchmarks (1385 ± 125 ms)
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
| assignToCohort, | ||
| captureShieldEligibilityCohortEvent, | ||
| ], | ||
| [], // Empty deps = always stable! |
There was a problem hiding this comment.
Callback can be no-op initially
High Severity
evaluateCohortEligibility now delegates to evaluateCohortEligibilityRef.current, which is only assigned inside a useEffect. Callers that invoke the callback during the commit phase (e.g., Home’s componentDidUpdate) can hit an uninitialized ref, making the call a silent no-op and potentially skipping Shield cohort evaluation permanently.
Builds ready [c65e21b]
⚡ Performance Benchmarks (1340 ± 100 ms)
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs
|


Description
ShieldSubscriptionProviderwas creating a new context value object on every render and recreatingevaluateCohortEligibilitywhenever any of its 10 dependencies changed, causing unnecessary re-renders of the Home component subtree.Two fixes applied:
Stabilized callback with ref pattern -- Moved the
evaluateCohortEligibilityimplementation into a ref updated viauseEffect. The public callback delegates to the ref, keeping a stable reference (useCallbackwith empty deps) while still accessing current closure values.Memoized context value -- Wrapped the provider value object in
useMemokeyed on the now-stable callback, preventing a new object reference on every render.Expected impact: ~60-70% reduction in re-renders for components consuming this context (Home page subtree).
Changelog
CHANGELOG entry: null
Related issues
Fixes: MetaMask/MetaMask-planning#6638
Manual testing steps
ShieldSubscriptionProviderconsumers do not re-render unnecessarily -- the context value reference should remain stable across parent re-renders.Screenshots/Recordings
Before
N/A -- no visual changes; this is a performance optimization.
After
N/A -- no visual changes; this is a performance optimization.
Pre-merge author checklist
Pre-merge reviewer checklist
Note
Medium Risk
Behavior should be equivalent, but ref-backed callbacks can hide stale/ordering bugs if the ref isn’t updated before consumers call it; changes affect Shield eligibility/modal flow triggers.
Overview
Improves
ShieldSubscriptionProviderperformance by makingevaluateCohortEligibilityand the provided context value stable across re-renders. The eligibility logic is moved into a ref updated viauseEffect, while a zero-depsuseCallbackdelegates to the ref so consumers don’t re-render when dependencies change.Adds a Jest test suite covering child rendering, context shape, memoization/stable references, and that the stable callback still reads current selector values when conditions change.
Written by Cursor Bugbot for commit c65e21b. This will update automatically on new commits. Configure here.