Skip to content

feat: implement safe state management for notification hooks#40401

Merged
Prithpal-Sooriya merged 2 commits intomainfrom
refactor-notif-hook-callbacks-avoid-react-errors
Feb 26, 2026
Merged

feat: implement safe state management for notification hooks#40401
Prithpal-Sooriya merged 2 commits intomainfrom
refactor-notif-hook-callbacks-avoid-react-errors

Conversation

@Prithpal-Sooriya
Copy link
Copy Markdown
Contributor

@Prithpal-Sooriya Prithpal-Sooriya commented Feb 25, 2026

Description

Root cause: whenever any team performs async local state updates and the component unmounts, you will receive this react error (this does eventually get cleaned up and isn't a breaking error).

E.g.

  1. Component Mounts
  2. Component calls an async function which once finished will update its local state (useState)
  3. Component unmounts
  4. Async function finished, and tries calling setState (but component was unmounted!) -- react error is logged (not breaking and eventually cleaned).

This update introduces a new useSafeState hook to manage state updates safely while components are mounted, preventing potential memory leaks and reduced the opportunity for React: State updates on unmounted components errors.

The useListNotifications, useCreateNotifications, useEnableNotifications, and useDisableNotifications hooks have been refactored to utilize useSafeState for loading, error, and notifications data states. This change enhances the stability and reliability of notifications handling in the application.

If this hook is useful, we can move this for other teams to use.

Open in GitHub Codespaces

Changelog

CHANGELOG entry: feat: implement safe state management for notification hooks

Related issues

Fixes: https://consensys.slack.com/archives/CTQAGKY5V/p1771942084169109 https://github.com/MetaMask/metamask-extension/actions/runs/22353733972/job/64687641620?pr=40327

Manual testing steps

Screenshots/Recordings

Before

After

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

Note

Low Risk
Small, localized React hook change that only affects how async notification actions update local state; minimal behavioral impact beyond preventing post-unmount state updates.

Overview
Adds an internal useSafeState hook in useNotifications.ts that gates setState calls behind a mounted check to avoid React warnings/memory leaks when async work completes after unmount.

Refactors useListNotifications, useCreateNotifications, useEnableNotifications, and useDisableNotifications to use useSafeState for loading/error/notificationsData, and updates useCallback dependency arrays accordingly.

Written by Cursor Bugbot for commit 2b153c2. This will update automatically on new commits. Configure here.

…s hooks

This update introduces a new `useSafeState` hook to manage state updates safely while components are mounted, preventing potential memory leaks. The `useListNotifications`, `useCreateNotifications`, `useEnableNotifications`, and `useDisableNotifications` hooks have been refactored to utilize `useSafeState` for loading, error, and notifications data states. This change enhances the stability and reliability of notifications handling in the application.
@github-actions
Copy link
Copy Markdown
Contributor

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.

*
* @param initialValue - The initial value of the state.
*/
function useSafeState<TValue>(
Copy link
Copy Markdown
Contributor Author

@Prithpal-Sooriya Prithpal-Sooriya Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can move this out in the future if other teams have similar state updates on unmounted component issues.

@metamaskbotv2
Copy link
Copy Markdown
Contributor

metamaskbotv2 bot commented Feb 25, 2026

✨ Files requiring CODEOWNER review ✨

🔔 @MetaMask/notifications (1 files, +46 -12)
  • 📁 ui/
    • 📁 hooks/
      • 📁 metamask-notifications/
        • 📄 useNotifications.ts +46 -12

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

@Prithpal-Sooriya Prithpal-Sooriya changed the title feat(notifications): implement safe state management for notification hooks feat: implement safe state management for notification hooks Feb 25, 2026
…ate hook

This update modifies the `useSafeState` hook to explicitly set `isMountedRef.current` to true upon component mount. This change enhances the reliability of state management within the notifications hooks, ensuring that state updates are only processed while the component is mounted, thereby preventing potential memory leaks.
@sonarqubecloud
Copy link
Copy Markdown

@metamaskbotv2
Copy link
Copy Markdown
Contributor

metamaskbotv2 bot commented Feb 25, 2026

Builds ready [2b153c2]
⚡ Performance Benchmarks (1386 ± 107 ms)
👆 Interaction Benchmarks
ActionMetricMean (ms)Std Dev (ms)P75 (ms)P95 (ms)
Load New Accountload_new_account2674269272
total2674269272
Confirm Txconfirm_tx60681360696089
total60681360696089
Bridge User Actionsbridge_load_page27340308314
bridge_load_asset_picker24949278329
bridge_search_token7355737742
total1255612571263
🔌 Startup Benchmarks
BuildMetricMean (ms)Min (ms)Max (ms)Std Dev (ms)P 75 (ms)P 95 (ms)
Chrome Browserify Startup Standard HomeuiStartup13861172170610714181576
load118099414289612201361
domContentLoaded117297914229612131351
domInteractive2817130202476
firstPaint175691241193207315
backgroundConnect20719224912209233
firstReactRender19115162129
initialActions108113
loadScripts98279412399610201163
setupStore1364151521
numNetworkReqs312290202286
Chrome Browserify Startup Power User HomeuiStartup2479139010115171722495880
load11521018171613911761528
domContentLoaded11361007163813311611490
domInteractive3418191283189
firstPaint1807447784248300
backgroundConnect746255770611394243234
firstReactRender24164862634
initialActions104112
loadScripts93180614001319451286
setupStore1566791731
numNetworkReqs70401542879129
Chrome Webpack Startup Standard HomeuiStartup92172712161169771179
load788637103896848961
domContentLoaded782631103096843955
domInteractive3017115222588
firstPaint1136634152128228
backgroundConnect282094103044
firstReactRender19134762231
initialActions105112
loadScripts779629102895840946
setupStore1363951324
numNetworkReqs312293202588
Chrome Webpack Startup Power User HomeuiStartup1233998181116213161569
load7236361264109713986
domContentLoaded7136301255108700964
domInteractive3619176293385
firstPaint1427052480179279
backgroundConnect17813137053172300
firstReactRender23183532428
initialActions103111
loadScripts7116281244106698955
setupStore1344871430
numNetworkReqs1113826951141198
Firefox Browserify Startup Standard HomeuiStartup17051420268723917072154
load14371196227220514551735
domContentLoaded14361190227220514511735
domInteractive92339099496143
firstPaint------
backgroundConnect63301902764120
firstReactRender14121711516
initialActions102012
loadScripts14071173224420114291710
setupStore188174201646
numNetworkReqs3120101202787
Firefox Browserify Startup Power User HomeuiStartup28702038793168730593724
load16461290657960616412363
domContentLoaded16451289657860616402363
domInteractive163361384183136564
firstPaint------
backgroundConnect4021091523302545944
firstReactRender211476112032
initialActions2052523
loadScripts16021267654660115952215
setupStore1328751175140563
numNetworkReqs74262034188161
Firefox Webpack Startup Standard HomeuiStartup17211465344426117572009
load14451177319324214821613
domContentLoaded14441177319324214811612
domInteractive108281858183131172
firstPaint------
backgroundConnect66272524171150
firstReactRender16122831625
initialActions102122
loadScripts14211162316923914541573
setupStore257252361669
numNetworkReqs311998182781
Firefox Webpack Startup Power User HomeuiStartup28192138564249929723574
load16261341371534616662179
domContentLoaded16251341371434616662179
domInteractive171381728226135671
firstPaint------
backgroundConnect37413312713024341031
firstReactRender24178192632
initialActions217123
loadScripts15861311367934216352161
setupStore16914797193211588
numNetworkReqs74302404685198
🧭 User Journey Benchmarks
BenchmarkMetricMean (ms)Std Dev (ms)P75 (ms)P95 (ms)
Onboarding Import WalletimportWalletToSocialScreen2181219220
srpButtonToSrpForm9439599
confirmSrpToPwForm2102121
pwFormToMetricsScreen1501515
metricsToWalletReadyScreen1501516
doneButtonToHomeScreen93334412401428
openAccountMenuToAccountListLoaded721850876307835
total851745188229034
Onboarding New WalletcreateWalletToSocialScreen2202220222
srpButtonToPwForm1093112114
createPwToRecoveryScreen9099
skipBackupToMetricsScreen3733940
agreeButtonToOnboardingSuccess1711718
doneButtonToAssetList91634411661430
total131233815651815
Asset DetailsassetClickToPriceChart4164348
total4164348
Solana Asset DetailsassetClickToPriceChart4814949
total4814949
Import Srp HomeloginToHomeScreen20088020822090
openAccountMenuAfterLogin4034144
homeAfterImportWithNewWallet21497422052230
total41979742314326
Send TransactionsopenSendPageFromHome1811819
selectTokenToSendFormLoaded2022224
reviewTransactionToConfirmationPage8514852855
total8956896904
SwapopenSwapPageFromHome1164119119
fetchAndDisplaySwapQuotes527784462296390
total539685763826508
🌐 Dapp Page Load Benchmarks

Current Commit: 2b153c2 | Date: 2/25/2026

📄 Localhost MetaMask Test Dapp

Samples: 100

Summary

  • pageLoadTime-> current mean value: 1.03s (±38ms) 🟡 | historical mean value: 1.04s ⬇️ (historical data)
  • domContentLoaded-> current mean value: 719ms (±36ms) 🟢 | historical mean value: 728ms ⬇️ (historical data)
  • firstContentfulPaint-> current mean value: 77ms (±9ms) 🟢 | historical mean value: 77ms ⬇️ (historical data)

📈 Detailed Results

Metric Mean Std Dev Min Max P95 P99
pageLoadTime 1.03s 38ms 1.01s 1.33s 1.06s 1.33s
domContentLoaded 719ms 36ms 695ms 1.00s 737ms 1.00s
firstPaint 77ms 9ms 60ms 152ms 84ms 152ms
firstContentfulPaint 77ms 9ms 60ms 152ms 84ms 152ms
largestContentfulPaint 0ms 0ms 0ms 0ms 0ms 0ms
Bundle size diffs
  • background: 58 Bytes (0%)
  • ui: 367 Bytes (0%)
  • common: 20 Bytes (0%)

@Prithpal-Sooriya Prithpal-Sooriya added this pull request to the merge queue Feb 26, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Feb 26, 2026
@Prithpal-Sooriya Prithpal-Sooriya added this pull request to the merge queue Feb 26, 2026
Merged via the queue into main with commit 933cd09 Feb 26, 2026
183 of 184 checks passed
@Prithpal-Sooriya Prithpal-Sooriya deleted the refactor-notif-hook-callbacks-avoid-react-errors branch February 26, 2026 12:02
@github-actions github-actions bot locked and limited conversation to collaborators Feb 26, 2026
@metamaskbot metamaskbot added the release-13.21.0 Issue or pull request that will be included in release 13.21.0 label Feb 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release-13.21.0 Issue or pull request that will be included in release 13.21.0 size-S team-assets

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants