10 KiB
Dependency upgrades
Backlog of upgrades that npx expo install --fix does not handle, with risk and test-coverage
notes so a later run can pick them up without redoing the research.
Last reviewed: 2026-10-29 (Expo SDK 58, react-native 0.88.1, all Expo-managed packages current).
How to refresh this list
npm_config_min_release_age=0 npx npm-check-updates
Then drop everything Expo manages: the keys in node_modules/expo/bundledNativeModules.json
plus the relatedPackages for the current SDK at https://api.expo.dev/v2/versions. Those only
move via npx expo install --fix or an SDK upgrade (see the README). Coverage numbers come from
npx jest --coverage.
Before bumping a critical package
Payments, auth, error reporting and the data layer are where a bad bump costs the most. Check the coverage notes here first. Where tests only exercise a mock, test the real flow on a device with a preview build before merging.
| Area | Packages | What tests cover | Gaps and what to do |
|---|---|---|---|
| Payments | @stripe/stripe-react-native |
Proof.test.tsx runs the whole purchase flow against Stripe's own Jest mock: happy path, non-default type, sheet cancelled, backend rejection, initPaymentSheet error. useProof.tsx and usePaymentSheetMutation.ts at 100%. |
The native SDK is mocked, so nothing proves the real sheet works. The 3DS/redirect return path (useDeepLinking in src/hooks.tsx, handleURLCallback) has no test. Expo-pinned, so it only moves with the SDK. Moved to 0.81.0 with SDK 58 on 2026-10-29, device check pending. When it does: run a test-mode purchase on Android and iOS, including one of Stripe's cards that requires authentication, to exercise the deep link. |
| Error reporting | @sentry/react-native |
Nothing. App.ts (init + Sentry.wrap) is outside the coverage scope and no test imports Sentry. |
Low risk within 8.x, but any breakage is invisible to CI. Expo-pinned since SDK 58. After a bump: preview build, trigger an error, confirm it lands in Bugsink. Self-hosted requirement for 8.x is already met since 8.20 works. Pending: neither the 8.24.0 bump (2026-09-05) nor 8.28.0 (SDK 58) has had this check yet. |
| Auth and storage | @react-native-async-storage/async-storage, src/auth/* |
tokenStorage.ts 100%, migrateFromReduxPersist.ts 100%, AuthContext.tsx 97%, all against AsyncStorage's official Jest mock. |
Good. The 2 to 3 major is Expo-pinned anyway; when SDK 58 brings it, the existing tests are the safety net. |
| Data layer | @tanstack/* |
src/queries 100% statements, queryClient.ts persistence 100%. jest.setup.js hooks into notifyManager.setScheduler, which is TanStack internals, so a breaking change there fails loudly. |
Good. Keep the three TanStack packages in lockstep. |
| Navigation | @react-navigation/* |
Navigation.tsx 100% as of 2026-09-05: Navigation.test.tsx renders the real tab + native-stack navigators and drives the header settings button; Login, Register, Membership, Settings and UserDelete tests render inside a NavigationContainer via testUtils.tsx. |
Good. Bump all three together; native-stack and bottom-tabs peer-require @react-navigation/native ^7.3.18. |
| Forms | react-hook-form |
FormInput.tsx, useLogin.tsx, useUserRegister.tsx, Login.tsx and UserRegister.tsx all at 100% statements and lines as of 2026-09-05: keyboard focus chain, stale-validity toast and its auto-dismiss, network and server errors, forgot-password link. |
Good. Note setFocus defers the focus call with a setTimeout, so tests wait for it via waitFor and read the focused field through focusedFieldLabel() in src/testUtils.tsx. |
| Notifications | expo-notifications |
useNotifications.ts 100% lines / 88% branches as of 2026-09-05: Settings.test.tsx toggles the reminder switch through denied, granted and off, asserting requestPermissionsAsync, cancelAllScheduledNotificationsAsync and scheduleNotificationAsync against the jest.setup.js stub. Only the "permission denied inside scheduling" early return (line 26) is unexercised. |
Expo-pinned, and the stub means nothing proves the native side. Test manually on SDK upgrades: grant permission, buy a membership, check the expiry reminder is scheduled. |
Ready to bump
Nothing outstanding as of 2026-10-29. Run npm test, npm run lint and npm run check-types
after any bump.
Done
| Date | Package | Change | Notes |
|---|---|---|---|
| 2026-10-29 | Expo SDK 57 → 58 | expo ~57.0.20 → ~58.0.21, react-native 0.86.3 → 0.88.1, react 19.2.3 → 19.3.0, jest 29 → 30, gesture-handler 2 → 3, Stripe 0.64.0 → 0.81.0, Sentry ~8.24.0 → ~8.28.0, Skia 2.6.2 → 2.14.0, plus every other Expo-pinned package |
Lockfile regenerated: the old one made npm fail with ERESOLVE (Sentry's optional expo-router peer). Strict TS API fixes: TextInputInstance ref in TextField, getInitialURL can return undefined, handleSubmit wrapped so RN press events aren't passed as DOM events, openURL resolves to void. Jest needs resolver: 'react-native-reanimated/jest/resolver' for Reanimated 4.7 (the manual Reanimated/Worklets mocks are still needed for one suite). Sentry is now in Expo's list, so the expo.install.exclude entry is gone. Added expo-build-properties: iOS useFrameworks: "dynamic" (Stripe ≥ 0.78 resolves the Stripe iOS SDK through SPM and fails pod install otherwise; disableSPM is no way out since CocoaPods stops at StripePaymentSheet 26.9.0 and 0.81 needs 26.13.0) and Android enableMinifyInReleaseBuilds: false (R8 is the SDK 58 default; see TODO.md). Device checks still outstanding, see TODO.md. |
| 2026-10-29 | @react-navigation/*, @tanstack/*, react-hook-form, @react-native-vector-icons/feather, prettier, eslint-plugin-prettier, @testing-library/react-native, test-renderer, eas-build-cache-provider |
native ^7.5.1, bottom-tabs/native-stack ^7.21.0, TanStack ^5.105.0, ^7.91.0, ^13.1.3 → ^14.0.0, ^3.9.10, ^5.5.7, ^14.1.0, ~1.3.1, ^25.3.0 → ^27.1.0 | Changelogs checked, nothing breaking for us. Feather 14 is a version-alignment release with no code changes. eas-cli 26/27 breaking changes (stderr output, artifact exports) don't touch the cache provider. |
| 2026-09-05 | react-hook-form |
^7.52.1 → ^7.87.0 | Lockfile already had 7.83.0, so the real step was 7.83 → 7.87. Login and UserRegister tests extended to 100% first; full suite, lint and types green after. |
| 2026-09-05 | @react-navigation/native, native-stack, bottom-tabs |
^7.1.17 → ^7.3.18, ^7.3.26 → ^7.18.10, ^7.4.7 → ^7.18.18 | Bumped together. Suite, lint, types and expo install --check green. |
| 2026-09-05 | @tanstack/react-query, react-query-persist-client, query-async-storage-persister |
^5.101.4 → ^5.102.8 | Bumped together. |
| 2026-09-05 | @sentry/react-native |
~8.20.0 → ~8.24.0 | 8.25.0 held back by the release-age gate (see Ready to bump). Not yet verified in a preview build against Bugsink. |
| 2026-09-05 | @react-native-vector-icons/feather |
^13.1.2 → ^13.1.3 | |
| 2026-09-05 | prettier, eslint-plugin-prettier, eslint-config-prettier |
^3.3.3 → ^3.9.6, ^5.1.3 → ^5.5.6, ^9.1.0 → ^10.1.8 | No reformatting fell out of the prettier bump; lint clean. eslint-config-prettier 10.1.6 and 10.1.7 were compromised releases, 10.1.8 is the clean one. |
| 2026-09-05 | @testing-library/react-native |
^13.3.3 → ^14.0.1, react-test-renderer replaced by test-renderer ~1.2.0 |
Codemod npx codemod@latest run rntl-v14-async-functions --target ./src --param customRenderFunctions=renderWithProviders --no-interactive --allow-dirty did 10 files; by hand: async fillForm/renderProof helpers, UNSAFE_*ByType replaced. Spinners now carry accessible accessibilityRole="progressbar" (role queries only see accessible elements). jest.setup.js overrides the preset's RefreshControl mock, which drops all props, so testID/onRefresh reach the host tree. Tests that observed a pre-resolve state now hold the promise (mockImplementationOnce with a deferred resolve) since async render flushes microtasks. Dropped the unreachable stale-isValid toast in UserRegister and its test. |
Needs work first
eslint 9 → 10 (blocked upstream)
eslint-config-expo 57.0.2 pulls eslint-plugin-import 2.32.0 and eslint-plugin-react 7.37.5,
whose latest releases still declare ESLint 9 as the maximum peer. npm refuses the install with
ERESOLVE. Recheck with:
npm view eslint-plugin-import peerDependencies.eslint
npm view eslint-plugin-react peerDependencies.eslint
The migration itself is small once unblocked: the config is already flat, and the only expected
fallout is three new rules in eslint:recommended (no-unassigned-vars, no-useless-assignment,
preserve-caught-error).
Expo-managed, do not bump by hand
npm-check-updates lists these too, but they are pinned by the SDK and expo install --check
currently reports them all as correct for SDK 58. Expect them to move with SDK 59:
- Majors waiting:
@react-native-async-storage/async-storage3,typescript7,@babel/core8 - Minors and patches:
react,@sentry/react-native(8.33),@stripe/stripe-react-native(0.84, which drops thedisableSPMoption entirely),react-native-gesture-handler,react-native-safe-area-context,react-native-screens,react-native-worklets @babel/coreis implied byexpoand could be removed frompackage.jsonat the next SDK upgrade
Gotchas
-
~/.npmrcsetsmin-release-age=3, so anything published in the last three days fails with "No matching version found ... with a date before ...". One-off bypass: prefix the command withnpm_config_min_release_age=0. -
react-native patch bumps: react-native pins
@react-native/jest-presetas an exact optional peer and npm refuses to swap the version already in the tree ("Conflicting peer dependency"), even vianpm updateor an explicit install. Fix without--legacy-peer-deps: install the new version as an exact dev dependency, then uninstall it again. The node stays because jest-expo needs it.npm i -D --save-exact @react-native/jest-preset@<version> npm uninstall -D @react-native/jest-preset -
Expo's patch releases are often under three days old when an SDK upgrade needs them, and
expo install --fixthen fails on the release-age gate. Bypassing it is fine for the SDK upgrade itself.