React Native Roadmap 2026-W21

Week of May 18–May 24, 2026

Items This Week

#TitleLabelLink
1Expo SDK 56 — RN 0.85, Expo UI stable, inline modules, faster builds🟧 EXPORead
2Hermes-node — Hermes CLI compatible with Node.js APIs🟦 RNRead
3WTF does .box() do in Nitro Modules?🟦 RNRead
4Hot Updater — self-hosted OTA updates with bundle diffing🟦 RNRead
5Storybook 10.4 — React Native isolation & agentic setup⚛️ REACTRead
6Relay 21.0 — first-party TypeScript support + experimental RSC⚛️ REACTRead

5-Day Action Plan

 


🟧 Chunk 1 — Upgrade to Expo SDK 56

Goal: Bring your project to Expo SDK 56 to unlock React Native 0.85, Hermes v1 as default engine, stable Expo UI, TypeScript 6, and build-time improvements of ~16% on iOS.

Scope:

  • Run npx expo install expo@^56.0.0 --fix to align all dependencies with SDK 56
  • Run npx expo-doctor@latest and fix any reported issues
  • Update iOS deployment target from 15.1 to 16.4 in your podspec (for custom native modules)
  • Handle breaking changes: expo/fetch is now the default global fetch — remove manual imports
  • Handle async copy()/move() breaking change in expo-file-system — replace with copySync()/moveSync() where synchronous behaviour is needed
  • Run the @expo/vector-icons codemod: npx @react-native-vector-icons/codemod to migrate to @react-native-vector-icons/* scoped packages
  • Rebuild the native project and test on both iOS and Android simulators

Out of scope: Adoption of inline modules, Expo Modules type generation (expo-type-information), brownfield multi-framework setup.

Dependencies: None — this is the foundation for subsequent chunks.

Acceptance criteria:

  • App boots and renders correctly on iOS 16.4+ and Android
  • npx expo-doctor@latest reports no critical errors
  • No @expo/vector-icons import errors in the build
  • iOS clean build time is measurably reduced (target: ≥ 50 s saved vs SDK 55)

Estimated effort: M

 

**Copy/paste this prompt:**

Implement the following React Native chunk for your mobile app: Upgrade the project from Expo SDK 55 to Expo SDK 56.

Goal: Align all dependencies with Expo SDK 56, apply breaking-change fixes, and verify the app compiles and runs on both platforms.

Files to create or modify:

  • package.json — bumped Expo and peer dependency versions
  • app.json / app.config.ts — update any SDK-version-specific config
  • podspec (custom native modules only) — bump iOS deployment target to 16.4
  • All files importing from @expo/vector-icons — migrate to @react-native-vector-icons/*
  • All files using synchronous copy()/move() from expo-file-system — switch to copySync()/moveSync()

Step-by-step instructions:

  1. Run npx expo install expo@^56.0.0 --fix and commit the resulting lockfile change.
  2. Run npx expo-doctor@latest and resolve every reported issue before continuing.
  3. Search for from '@expo/vector-icons' across the codebase and run npx @react-native-vector-icons/codemod to auto-migrate.
  4. Search for .copy( and .move( calls from expo-file-system and replace asynchronous usages correctly; use copySync/moveSync only where truly synchronous behaviour is required.
  5. If you have custom native modules, update the iOS deployment target to 16.4 in every .podspec file.
  6. Delete ios/ and android/ generated directories (if using CNG / Continuous Native Generation) and re-run npx expo prebuild.
  7. Run npx expo run:ios and npx expo run:android and verify the app launches without errors.

Acceptance criteria checklist:

  • npx expo-doctor@latest exits with 0 errors
  • App launches on iOS 16.4 simulator without crash
  • App launches on Android emulator without crash
  • No remaining @expo/vector-icons imports in source
  • expo/fetch is used as the global fetch without manual imports

🟦 Chunk 2 — Implement Hot Updater for Self-Hosted OTA Updates

Goal: Deploy JavaScript-only changes to production without an app store submission, using Hot Updater's self-hosted OTA with bundle diffing to reduce update payload size by up to 58%.

Scope:

  • Install hot-updater and its server-side package
  • Configure a storage provider (S3-compatible bucket or a lightweight Node server) to host bundles
  • Enable Hermes bytecode bundle diffing in Hot Updater config
  • Add the in-app update check (foreground trigger) with a non-blocking UX
  • Wire a publish script into your CI pipeline (GitHub Actions or similar) that runs on merge to main

Out of scope: Native code updates, EAS Build integration, rollback UI, user-facing update progress bar.

Dependencies: Expo SDK 56 (Chunk 1) is recommended; Hot Updater also works standalone with bare React Native.

Acceptance criteria:

  • A JS-only change (e.g., a button label) is pushed via hot-updater publish and appears on device without a new binary release
  • The published bundle diff is verifiably smaller than the full bundle in CI logs
  • Update check fires on app foreground and silently applies the patch on next restart
  • The app falls back gracefully if the update server is unreachable

Estimated effort: M

 

**Copy/paste this prompt:**

Implement the following React Native chunk for your mobile app: Set up Hot Updater for self-hosted Over-the-Air (OTA) updates with bundle diffing.

Goal: Allow JS-only changes to reach production devices without an app store submission.

Files to create or modify:

  • package.json — add hot-updater dependency
  • app.json / app.config.ts — add the Hot Updater config plugin
  • App.tsx (or entry point) — wrap the app with HotUpdater.wrap and configure the update check
  • .github/workflows/publish.yml — add a CI job that runs hot-updater publish on merge to main
  • hot-updater.config.ts (new file) — define storage provider, bundle diffing enabled

Step-by-step instructions:

  1. Install: npx expo install hot-updater.
  2. Add hot-updater to the plugins array in app.json with your storage provider credentials.
  3. Create hot-updater.config.ts at the project root, set bundleDiff: true and configure your S3 bucket or compatible provider.
  4. Wrap your entry component: export default HotUpdater.wrap({ source: 'https://your-bucket/updates' }, App).
  5. Add a foreground-check trigger using AppState to call HotUpdater.checkForUpdate() when the app comes to the foreground.
  6. Add a GitHub Actions step: after all tests pass on main, run npx hot-updater publish --platform ios --platform android.
  7. Test locally: make a visible JS change, run npx hot-updater publish, install a previous build on a simulator, launch it and verify the update is applied.

Acceptance criteria checklist:

  • hot-updater publish completes without errors in CI
  • Bundle diff size logged in CI is smaller than full bundle size
  • A JS-only change appears on device without a new binary
  • App renders normally when the update server returns 404 (no update available)
  • App renders normally when the update server is unreachable (network off)

🟧 Chunk 3 — Migrate Community Components to Expo UI Drop-in Replacements

Goal: Replace a set of popular community UI libraries with Expo UI's stable, SwiftUI/Jetpack Compose-backed drop-in replacements to reduce bundle size, eliminate legacy UIKit wrappers, and gain production-ready native fidelity.

Scope:

  • Audit the codebase for usage of: @react-native-community/datetimepicker, @gorhom/bottom-sheet, @react-native-picker/picker, @react-native-community/slider, @react-native-segmented-control/segmented-control
  • Replace each import with its @expo/ui/community/* equivalent (in most cases, only the import path changes)
  • Document any props that are unsupported or behave differently in Expo UI and adjust accordingly
  • Validate on both iOS and Android simulators

Out of scope: Web support (Expo UI web APIs are still experimental), custom theming beyond platform defaults, SwiftUI useNativeState integration.

Dependencies: Expo SDK 56 (Chunk 1).

Acceptance criteria:

  • All migrated component usages render pixel-correctly on iOS and Android
  • No remaining imports from @gorhom/bottom-sheet, @react-native-community/datetimepicker, @react-native-picker/picker
  • npx expo-doctor reports no peer-dependency conflicts from removed libraries
  • package.json no longer lists replaced libraries as dependencies

Estimated effort: S

 

**Copy/paste this prompt:**

Implement the following React Native chunk for your mobile app: Migrate community UI components to Expo UI drop-in replacements available in SDK 56.

Goal: Swap heavy community wrappers for Expo UI's native SwiftUI/Jetpack Compose-backed equivalents by changing only the import path.

Files to create or modify:

  • All files importing from @react-native-community/datetimepicker → replace with @expo/ui/community/datetime-picker
  • All files importing from @gorhom/bottom-sheet → replace with @expo/ui/community/bottom-sheet
  • All files importing from @react-native-picker/picker → replace with @expo/ui/community/picker
  • All files importing from @react-native-community/slider → replace with @expo/ui/community/slider
  • All files importing from @react-native-segmented-control/segmented-control → replace with @expo/ui/community/segmented-control
  • package.json — remove replaced libraries from dependencies after migration

Step-by-step instructions:

  1. Run a global search for each community library import across src/, app/, and components/.
  2. For each match, change only the import path to the @expo/ui/community/* equivalent. Do not change component names or props in the first pass.
  3. Build the app (npx expo run:ios && npx expo run:android) and check for TypeScript errors related to unsupported props.
  4. For any unsupported prop, check the Expo UI SDK 56 docs and either remove the prop, find the equivalent, or add a TODO comment for follow-up.
  5. Manually test each migrated component on both platforms in a representative user flow.
  6. Remove the now-unused community libraries from package.json and run npx expo install --fix.

Acceptance criteria checklist:

  • No build errors related to replaced component imports
  • DateTimePicker opens and returns a valid date on iOS and Android
  • BottomSheet animates open and closed without jitter
  • Picker and Slider respond to user input correctly
  • package.json no longer lists replaced packages

⚛️ Chunk 4 — Set Up Storybook 10.4 with React Native Isolation

Goal: Upgrade to Storybook 10.4 and enable the new cleaner React Native isolation mode to develop and review UI components independently from business logic and backend dependencies.

Scope:

  • Upgrade Storybook to 10.4 using npx storybook@latest upgrade
  • Configure @storybook/react-native with the improved 10.4 setup (reduced boilerplate, no manual register step)
  • Write 3 component stories for key UI components (e.g., Button, Card, TextInput)
  • Configure TanStack Query mock provider in .storybook/preview.tsx if TanStack Query is used
  • Verify the Storybook app launches in the Expo dev client

Out of scope: Chromatic visual regression testing, CI integration, Storybook Web build.

Dependencies: None — Storybook can be set up independently of the SDK upgrade.

Acceptance criteria:

  • Storybook launches in the Expo dev client on both iOS and Android without errors
  • All 3 component stories render correctly in isolation (no network calls, no navigation context required)
  • Switching between stories updates the rendered component without a full app reload
  • The isolation mode correctly mocks providers so components are self-contained

Estimated effort: S

 

**Copy/paste this prompt:**

Implement the following React Native chunk for your mobile app: Upgrade to Storybook 10.4 and configure React Native isolation mode.

Goal: Enable isolated component development with Storybook 10.4's new React Native setup.

Files to create or modify:

  • package.json — bump @storybook/react-native to ^10.4.0 and related addons
  • .storybook/main.ts — update configuration to use the 10.4 framework preset
  • .storybook/preview.tsx — configure global decorators (ThemeProvider, QueryClientProvider, NavigationContainer mock)
  • src/components/Button/Button.stories.tsx (new) — 3 stories: Default, Disabled, Loading
  • src/components/Card/Card.stories.tsx (new) — 3 stories: Basic, WithImage, Skeleton
  • src/components/TextInput/TextInput.stories.tsx (new) — 3 stories: Empty, WithValue, Error state

Step-by-step instructions:

  1. Run npx storybook@latest upgrade and follow the migration prompts.
  2. In .storybook/main.ts, ensure the framework is set to @storybook/react-native and the addons list includes @storybook/addon-ondevice-controls.
  3. In .storybook/preview.tsx, wrap all stories in a decorator that provides: a mock QueryClient, a mock NavigationContainer, and your app's ThemeProvider.
  4. Create Button.stories.tsx with Default, Disabled, and Loading stories using argTypes for interactive controls.
  5. Create Card.stories.tsx and TextInput.stories.tsx similarly.
  6. Run the Storybook entry point via Expo dev client: STORYBOOK_ENABLED=true npx expo start and verify stories load.

Acceptance criteria checklist:

  • Storybook app launches without JS errors on iOS and Android
  • Button stories show 3 variants controllable via the addon panel
  • Card stories show 3 variants without network calls
  • TextInput stories show error state styling without a running API
  • Switching stories does not cause a full app reload

🟦 Chunk 5 — Use .box() in Nitro Modules for Worklet-Crossing Native State

Goal: Leverage the .box() API in Nitro Modules to transfer NativeState-backed objects across worklet runtime boundaries, enabling synchronous, zero-serialization communication between native and the UI thread.

Scope:

  • Read and understand the .box() / NativeState / HostObject model (source article)
  • Identify a concrete use case in your app: for example, a camera state controller, audio session state, or animation value holder
  • Implement or extend a Nitro module to expose a NativeState object that can be .box()-ed and passed to a Reanimated worklet
  • Test synchronous access from a worklet (e.g., reading a native value during a gesture handler) on both platforms

Out of scope: Creating a brand-new full Nitro module from scratch, SwiftUI useNativeState integration, publishing the module as an npm package.

Dependencies: React Native New Architecture (mandatory for Nitro Modules); Reanimated 3.x for worklet testing.

Acceptance criteria:

  • A NativeState object is boxed with .box() and accessible from a Reanimated worklet without JS-thread serialization
  • Reading the boxed state from a gesture handler produces no UI jitter or lag
  • No crashes on rapid gesture interactions on iOS and Android
  • The worklet can read the native value synchronously and use it to drive an animation

Estimated effort: M

 

**Copy/paste this prompt:**

Implement the following React Native chunk for your mobile app: Use .box() in Nitro Modules to pass NativeState objects across worklet runtime boundaries.

Goal: Enable synchronous, zero-serialization native state access from Reanimated worklets using Nitro Modules' .box() API.

Files to create or modify:

  • modules/MyNitroModule/src/MyNitroModule.nitro.ts — declare a method returning a NativeState that exposes a .box() method
  • modules/MyNitroModule/ios/MyNitroModule.swift — implement the Swift NativeState class and the .box() return
  • modules/MyNitroModule/android/MyNitroModule.kt — implement the Kotlin equivalent
  • src/hooks/useBoxedNativeState.ts (new) — a React hook that calls the Nitro module and holds the boxed reference
  • src/components/MyAnimatedComponent.tsx — a component that passes the boxed state to a Reanimated worklet via useSharedValue or runOnUI

Step-by-step instructions:

  1. In your Nitro module spec (*.nitro.ts), add a method getState(): NativeState<MyState> where MyState includes the value(s) you want to share.
  2. Implement MyState in Swift/Kotlin so it conforms to the Nitro NativeState protocol, exposing the relevant fields.
  3. In the Nitro module host method, return the NativeState wrapped via .box() so it becomes a HostObject.
  4. In useBoxedNativeState.ts, call MyNitroModule.getState() on mount and store the returned HostObject in a useSharedValue.
  5. In MyAnimatedComponent.tsx, use useAnimatedGestureHandler or useAnimatedStyle and read the boxed state's fields directly inside the worklet callback.
  6. Run the component on a physical device (or high-fidelity simulator) and perform rapid gestures to verify no UI jitter.

Acceptance criteria checklist:

  • The Nitro module compiles on both iOS and Android with no errors
  • The boxed NativeState is accessible from inside a runOnUI worklet
  • Reading a native value from the worklet does not cause frame drops (check with Reanimated FPS overlay)
  • No crash occurs after 30+ rapid gesture interactions on iOS
  • No crash occurs after 30+ rapid gesture interactions on Android