feat: React Native Upgrade / 0.81.5 #29195
Conversation
The global testSetup.js BottomSheet mock covers this. Only 1 of 27 files with local BottomSheet mocks was actually redundant — the rest mock different BottomSheet-related components or need BottomSheetHeader/ BottomSheetFooter mocks that the global mock doesn't provide.
The tooltip press works fine — the skip comment was overly cautious.
Move mockReanimated from a runtime function to a proper jest.mock so it takes effect before component import. Restores the original assertions checking actual animation values (height, opacity) instead of weak "style exists" checks.
… version mismatch during snap WebView teardown
…me/Jest ESLint deps Adds browserify-zlib for Metro extraNodeModules zlib shim. Removes enzyme stack and eslint-plugin-jest which were unused in the codebase.
patch-package failed in CI when @types/enzyme is not installed; the types package is no longer a dependency. Made-with: Cursor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1a18a5e. Configure here.
| ~/Library/Developer/Xcode/DerivedData | ||
| ios/build | ||
| key: ${{ runner.os }}-xcode-main-${{ env.XCODE_CACHE_VERSION }}-${{ hashFiles('ios/**/*.{h,m,mm,swift}', 'ios/**/Podfile.lock', 'yarn.lock') }} | ||
| key: ${{ runner.os }}-xcode-main-${{ env.XCODE_CACHE_VERSION }}-${{ hashFiles('ios/**/*.{h,m,mm,swift}', 'ios/**/Podfile.lock', 'yarn.lock') }}-${{ github.run_id }} |
There was a problem hiding this comment.
Main cache fallback will never hit due to run_id
Medium Severity
The "Restore from main cache" fallback steps append -${{ github.run_id }} to their cache keys, but the main branch was built under a different run_id. Since cache keys must match exactly, these fallback lookups will never produce a cache hit. The github.run_id suffix is intentional on the branch cache keys (to force fresh builds), but applying it to the main fallback keys defeats the purpose of falling back to a known-good main build. The main fallback keys need to omit the run_id suffix, or the fallback is dead code and every branch build always does a full rebuild even when main has a valid cached artifact.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 1a18a5e. Configure here.
The production code uses parseJsonOrThrow which calls response.text() but the fetch mocks only provided json(). Added text() to all three failing mock responses. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…MetaMask/metamask-mobile into rn-upgrade/0.81.5-no-unit-tests
Align conflicted files with upstream main: - build-android-e2e.yml: take namespace runner support, reset CACHE_GENERATION to v1 - bitrise.yml: take upstream VERSION_NUMBER/FLASK_VERSION_NUMBER (4823) - project.pbxproj: take upstream CURRENT_PROJECT_VERSION (4823) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…MetaMask/metamask-mobile into rn-upgrade/0.81.5-no-unit-tests
… plain style objects
…ce, args.join, => args) across 15 test files
…o-unit-tests # Conflicts: # yarn.lock
…o-unit-tests # Conflicts: # android/app/build.gradle
…e in BenefitCard test
🔍 Smart E2E Test Selection⏭️ Smart E2E selection skipped - skip-smart-e2e-selection label found All E2E tests pre-selected. |
|




Description
Upgrades the core mobile stack from React Native 0.76.9 to 0.81.5,
React 18.3 to 19.1, and Expo SDK 52 to 54. Includes all necessary
migration fixes for breaking changes in the new architecture, test
infrastructure updates, and dependency alignment.
Changelog
Core stack
react-test-rendererto match)expo-*package bumped to its SDK 54 line)@react-native-community/cli(+cli-platform-android/-ios) 15.0.1 → 20.0.0@react-native/metro-config0.76.9 → 0.81.5@types/react^18.2.6 → ^19.1.0main.m) to Swift (AppDelegate.swift) as required by the RN 0.81 iOS templateReact Native ecosystem libraries
react-native-screens3.37.0 → 4.16.0 (major)react-native-safe-area-context^5.4.0 → ~5.6.0react-native-reanimated^3.17.2 → 3.19.0react-native-gesture-handler^2.25.0 → ~2.28.0react-native-mmkv^3.2.0 → ^4.1.2 (major; resolves to 4.3.1)react-native-pager-view^6.7.0 → 6.9.1react-native-nitro-modules^0.29.6 → 0.35.5react-native-svg^15.11.1 → 15.12.1react-native-video^6.10.1 → ^6.19.0react-native-vision-camera^4.6.4 → ^4.7.3react-native-view-shot^3.1.2 → 4.0.3 (major; legacypatch-packagepatch dropped)react-native-qrcode-svg5.1.2 → 6.3.21 (major; legacy patch dropped)react-native-performance^5.1.2 → ^6.0.0 (major; legacy patch dropped)react-native-release-profiler^0.4.0 → ^0.4.4@react-native-community/slider^4.4.3 → 5.0.1 (major)@react-native-async-storage/async-storage^1.23.1 → 2.2.0 (major)@react-native-community/viewpagernew direct dep (patched) to keep legacy paged-view consumers building under RN 0.81lottie-react-native6.7.2 → ~7.3.1 (major)rive-react-native9.3.4 → ^9.8.0 (legacy patch dropped)Native / platform integrations
@sentry/react-native~6.15.0 → ~7.2.0@segment/analytics-react-native^2.20.3 → ^2.21.4@solana-mobile/mobile-wallet-adapter-protocol^2.2.5 added (replaces the removed Solana shim path)Tooling / test infra
detox^20.35.0 → ^20.43.0@expo/fingerprint^0.15.0 → ~0.15.4pretty-format/react-ispinned to ^19.0.0 (resolution; required for React 19 test compat)tests/module-mocking/sentry/react-native.ts(E2E-only Sentry shim selected bymetro.config.jswhenIS_TEST=true)tsconfig.jsonpath aliases for selected@metamask/*dist subpathsBreaking Changes Addressed
React 19
defaultPropsfrom all function components (deprecated in React 18.3, removed in 19)useRefnow requires an explicit initial value — everyuseRef<T>()updated touseRef<T>(null)RefObject<T>typing tightened — call sites switched toRefObject<T | null>where they were assuming non-nullReact.cloneElementgenerics tightened — added explicit type parameters where inference no longer narrowsReactChildremoved — switched toReactNode@testing-library/react-hooks(unmaintained on React 19) to@testing-library/react-nativerenderHook{}toundefinedto match the newforwardRefsignaturererender()call sites to the new return shapeReact Native 0.81
BackHandler.removeEventListenershim (the static method was removed;addEventListenernow returns a subscription)AppDelegate.swift+ newLaunchScreen.xib(deletedmain.m)MMKVis now a type-only export and.remove()was renamed to.delete()(react-native-mmkvv4)NavigationContainerRefrequires a generic parameter (@react-navigation/nativev7)BlurEvent/FocusEventtyping changes onTextInputcallbacksrefreshControlprop typing on virtualized listsMessageEvent(private) usage inWebSocketis internal — Snaps continue to boot via the existingpatches/@metamask+post-message-stream+10.0.0.patch(no shim needed)@metamask/react-native-webview,@metamask/react-native-button,@metamask/react-native-payments,@metamask/react-native-acmfor RN 0.81 / React 19 compatibility (see TODO list below)expo-modules-coreEventEmittermock,Linkingmock path (now.default.openURL), safe-area / screens snapshotsSnaps
AbstractExecutionServicerenamed toExecutionServicein@metamask/snaps-controllersXMLSerializerandDOMParserinside the snap iframe via the new yarn patch on@metamask/snaps-execution-environments(replaces the runtime HTML mutation we previously carried inSnapsExecutionWebView.tsx)pumpusage in snap/background bridges (rolled back local stream-cleanup defensive code inMobilePortStream.jsandexecution-service-init.ts)Test infrastructure
@expensify/react-native-wallet)isClosingRefguard inBottomSheetDialogto prevent doublegoBack(MUSD-406) and rolled back a faultylodash.debounceattemptPolymarketProviderfetch mocks (missingresponse.text())useTokenSearchthrottle timing,EarnLendingWithdrawalmock orderinginitial-background-state.jsonfixture,useTailwindmock,reanimatedLoggerconfigBuild / CI
Podfile.lockregenerated for RN 0.81's new Pod graphPrivacyInfo.xcprivacyupdated for the SDK 54 surfaceCMAKE_VERSIONpinned for RN 0.81's NDK build${{ github.run_id }}to native-build cache keys (Android APKs/Gradle, iOS DerivedData) to force fresh builds during the upgrade rollout.depcheckrc.ymlignores extended for short-name Babel pluginsDependencies Patched (
.yarn/patches/)MetaMask-owned (see TODO below — should be upstreamed and the patches dropped):
@metamask/react-native-acm@1.2.0@metamask/react-native-button@3.0.0@metamask/react-native-payments@2.0.2@metamask/react-native-webview@14.6.0@metamask/snaps-execution-environments@11.0.2Third-party (need upstream PRs or version bumps as they ship RN 0.81-compatible releases):
react-native@0.81.5(RN itself — small follow-up patch on top of 0.81.5)@react-native-community/viewpager@3.3.0@braze/react-native-sdk@19.1.0(ReactModuleInfo signature change)react-native-aes-crypto-forked(we maintain the fork; can be folded into the fork repo)react-native-fast-crypto@2.2.0react-native-gzip@1.1.0react-native-i18n@2.0.15(deprecated upstream — long-term replace)react-native-os@1.2.6react-native-sensors@5.3.0reactotron-core-client@2.9.7expo-web-browser@14.0.2Legacy
patches/(patch-package) entries removed in this PRbecause the upstream packages or our own forks now cover them, or
because the patched version is no longer installed:
@metamask+react-native-button+3.0.0,@metamask+react-native-payments+2.0.0,react-native+0.76.9,react-native-aes-crypto-forked+1.2.1,react-native-fast-crypto+2.2.0,react-native-performance+5.1.2,react-native-qrcode-svg+5.1.2,react-native-view-shot+3.8.0,expo-updates-npm-0.27.4. Net result: no orphan patches carried forward.TODO — follow-ups after this PR is merged
Several Yarn patches added by this upgrade target packages we own under
@metamask/*. They unblock RN 0.81 today, but we should publish fixedreleases and drop the local patches in follow-up PRs:
@metamask/react-native-acm@1.2.0— RN 0.81's Kotlin rewrite ofReactContextBaseJavaModuleremoved thecurrentActivitysyntheticaccessor (now
getCurrentActivity()) and madeonNewIntent(intent: Intent)non-nullable. Patch updates the 4 affected sites in
GoogleAcmModule.kt.→ Publish a 1.x compatible release that compiles against RN 0.81.
@metamask/react-native-button@3.0.0—TouchableOpacity.propTypes,Text.propTypesandViewPropTypes.styleno longer exist on RN 0.81 /React 19. Patch falls back to
PropTypes.any/PropTypes.booldefensively.→ Replace
propTypeswith TypeScript types or PropTypes.shape literalsupstream.
@metamask/react-native-payments@2.0.2— Bumps AndroidcompileSdk/buildTools/targetSdkfrom 28 → 36 and drops the removedReactBridgeimport. → Publish a release with the SDK bump (andoptionally migrate the module to Kotlin / TurboModules while we're there).
@metamask/react-native-webview@14.6.0— AddscodegenConfig.ios.componentProvider/modulesProviderentries needed byRN 0.81's Fabric codegen so
RNCWebViewandRNCWebViewModuleregistercorrectly. → Cherry-pick into the next published release; this is purely
a
package.jsonmetadata fix.@metamask/snaps-execution-environments@11.0.2— AddsXMLSerializerand
DOMParserto the LavaMoatscuttleGlobalThis.exceptionslist indist/webpack/webview/index.htmlso snaps can run inside the iframeafter lockdown. (This replaces the runtime HTML mutation we previously
carried in
SnapsExecutionWebView.tsx.) → Land the equivalent change insnapsupstream so the scuttle exceptions are baked into the publishedHTML.
Other notable patches added here target third-party packages we don't own
(see the "Third-party" list above); they'll need separate upstream PRs or
version bumps as those projects publish RN 0.81-compatible releases.
CHANGELOG entry: null
Manual testing steps
Because this PR moves the entire native floor (RN 0.81 / React 19 / Expo 54),
the surfaces most likely to regress are the ones that cross the JS↔native
bridge or rely on WebViews, animations, and platform integrations. Run the
scenarios below on both iOS and Android (release-style build preferred so
LavaMoat lockdown and Hermes kick in).
The
[E2E Sentry Mock]log line should NOT appear in any of the dev/releasebuilds above; if it does,
IS_TEST=trueis leaking into the bundler env.Pre-merge author checklist
Performance checks (if applicable)
trace()for usage andaddTokenfor an exampleFor performance guidelines and tooling, see the Performance Guide.
Pre-merge reviewer checklist
Note
High Risk
High risk because it updates core React Native/Android build toolchain and introduces multiple Yarn patches to native dependencies, which can affect app startup, native compilation, and E2E reliability across both platforms.
Overview
Upgrades the RN stack to
react-native@0.81.5and aligns native build tooling around it: Android moves tocompileSdk/targetSdk 36, Kotlin2.1.20, NDK27.1, Gradle8.14.3, updates app initialization toloadReactNative(this), and adds an MLKit dependency override plus Detox flavor handling.Stabilizes CI/E2E builds during the upgrade by forcing fresh native caches (cache keys include
${{ github.run_id }}), pinning CMake for Android E2E builds, bumping iOS cache versions, increasing E2E timeout, and restoring iOS app/framework execute permissions after artifact download.Introduces/updates multiple
.yarn/patches/*to keep third-party and MetaMask native modules building under RN 0.81 (Gradle/SDK bumps, Kotlin/ObjC signature fixes, codegen metadata, safer JS snippets), removes obsolete patches, and adjusts JS/TS code for React 19 typing/runtime changes (BackHandler subscription cleanup, stricterRefObject<T | null>/useRefinit, safer prop spreads/cloneElement generics, default props moved to parameters, and test expectation updates).Reviewed by Cursor Bugbot for commit c3c6e6e. Bugbot is set up for automated code reviews on this repo. Configure here.