# 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 . 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 | `expo-web-browser` (Stripe Checkout) | `Proof.test.tsx` runs the purchase flow against a mocked `openAuthSessionAsync`: first purchase, renewal, non-default type, cancel on the Checkout page, dismissed browser, backend rejection, browser failure. `useProof.tsx` 100%, `requestStripeCheckoutSession` covered in `api.test.ts`. | The browser hop and galtinn's return page are mocked, so after an `expo-web-browser` bump run a test-mode purchase on Android and iOS, including a card that requires 3D Secure. `@stripe/stripe-react-native` was removed on 2026-10-29 when payments moved to Checkout. | | 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-storage` 3, `typescript` 7, `@babel/core` 8 - Minors and patches: `react`, `@sentry/react-native` (8.33), `react-native-gesture-handler`, `react-native-safe-area-context`, `react-native-screens`, `react-native-worklets` - `@babel/core` is implied by `expo` and could be removed from `package.json` at the next SDK upgrade ## Gotchas - `~/.npmrc` sets `min-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 with `npm_config_min_release_age=0`. - react-native patch bumps: react-native pins `@react-native/jest-preset` as an exact optional peer and npm refuses to swap the version already in the tree ("Conflicting peer dependency"), even via `npm update` or 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@ 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 --fix` then fails on the release-age gate. Bypassing it is fine for the SDK upgrade itself.