React Native Upgrade Helper: How to Upgrade React Native Without Pain (2026 Guide)

How to use the React Native Upgrade Helper: library checks, a 0.87 to 0.88 SceneDelegate walkthrough, git apply, the Expo path and cache fixes.

React Native Upgrade Helper: How to Upgrade React Native Without Pain (2026 Guide)

The worst React Native upgrade I ever did was eleven minor versions in one pull request. Three days of Gradle errors, a CocoaPods lockfile nobody understood, and a native module that had quietly been abandoned two years earlier. The best one took forty minutes, and the difference was not luck. It was using the React Native Upgrade Helper properly, one version hop at a time, on a branch, with the library check done before touching a single native file.

This guide is the process we use. It covers what the Upgrade Helper actually shows you, a worked example of the 0.87 to 0.88 upgrade (the SceneDelegate one), the Expo path, how to check third-party libraries first, and the cache-clearing routine for when the build still fails. Facts checked on October 26, 2026: React Native 0.88.0 is the latest stable and 0.85.x is now unsupported, Expo SDK 58 ships 0.88, and @react-native-community/cli is 20.2.0.

What the Upgrade Helper is

The Upgrade Helper is a web page that shows the full diff between two versions of the React Native app template. Under it sits rn-diff-purge, a repository that generates a fresh app called RnDiffApp for every release and stores the diff between every pair of versions. You pick a “from” and a “to” version, and it shows every file the template changed: package.json, Gradle files, AppDelegate, Podfile, Info.plist, Metro and Babel config. Many hunks carry comments explaining why the change exists, and major versions show a “useful content” box with links to the release notes.

Two settings people skip and then regret: you can enter your app name and Android package name, so the diff shows com.yourcompany.app instead of com.rndiffapp. Do it. It turns the page from a reference into something you can follow line by line.

What it is not: it does not know about your code, your native modules or your custom Gradle setup. It shows what a brand new app would look like on the target version. Your job is to bring your project to that shape.

The upgrade command is gone

Older guides tell you to run npx react-native upgrade. The current community CLI (20.2.0) ships init, doctor, info, clean, config and the platform build commands, and no upgrade command. The official upgrading docs point at the Upgrade Helper for CLI projects and at the Expo SDK walkthrough for Expo projects. That is the whole toolset, and honestly it is the better one: an automatic merge into a native project you have customised was always a coin flip.

Before you touch anything: check the libraries

The template diff is rarely what breaks. Native dependencies are. Before opening the Upgrade Helper:

  1. List native dependencies. Anything with an ios or android folder in node_modules. These are the ones that can fail to compile.
  2. Check each on React Native Directory for New Architecture support and last release date. A library with no release in 18 months is a decision you make now, not during the upgrade.
  3. Read each library’s changelog for a version that names your target React Native version. After 0.88 landed, Stream Chat, Tamagui and Hot Updater all shipped compatibility releases within a week; the long tail takes longer.
  4. In a monorepo, @rnx-kit/align-deps (5.0.1) checks that package versions line up with a React Native “profile”, which catches the classic mismatch where one workspace still pins an old Reanimated.

The community has also started building pre-flight tools for exactly this step. This month someone shared a free CLI that reports what will break before you upgrade. Treat any such tool as a checklist generator, not a guarantee.

The process, step by step

  1. Branch. git switch -c upgrade/rn-0.88. Commit a clean working tree first so the diff of the upgrade is reviewable.
  2. Read the release post for every version you cross, and the breaking changes section in particular. If you are more than two minors behind, hop one minor at a time and commit after each hop builds on both platforms. It is slower to type and much faster to debug.
  3. Open the Upgrade Helper with your current and target versions, your app name and package name.
  4. Update package.json first. Install exactly the versions the diff shows for react-native, react and the @react-native/* packages:
    npm install [email protected] [email protected] @react-native/[email protected]
    npm install -D @react-native/[email protected] @react-native/[email protected] \
      @react-native/[email protected] @react-native/[email protected]
    

    Use the dev dependency versions shown in your diff; the list above is what a stock template carries.

  5. Apply the native file changes hunk by hunk. Android Gradle and Kotlin versions, then iOS.
  6. Reinstall pods: cd ios && bundle exec pod install.
  7. Build both platforms from clean, run the app, and run your test suite. Then upgrade the native libraries you flagged earlier.

Worked example: 0.87 to 0.88 and the SceneDelegate

The 0.88 release has no user-facing breaking changes, and the template diff is only five files. It is still the upgrade you should not skip, because it moves iOS to the UIScene lifecycle that Apple requires from the iOS 27 SDK: UIKit apps built with Xcode 27 fail to launch without it. Here is what the Upgrade Helper shows.

AppDelegate.swift shrinks to almost nothing. The React Native factory, the window and the bundle URL all move out:

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
  func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {
    true
  }
}

A new SceneDelegate.swift takes over, and you must add it to the Xcode target (the diff’s project.pbxproj hunk does this for the template; in your project, add the file through Xcode so the IDs are correct):

import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import UIKit

class SceneDelegate: RCTDefaultReactNativeFactoryDelegate, UIWindowSceneDelegate {
  var window: UIWindow?
  var reactNativeFactory: RCTReactNativeFactory?

  func scene(
    _ scene: UIScene,
    willConnectTo session: UISceneSession,
    options connectionOptions: UIScene.ConnectionOptions
  ) {
    guard let windowScene = scene as? UIWindowScene else { return }

    dependencyProvider = RCTAppDependencyProvider()
    reactNativeFactory = RCTReactNativeFactory(delegate: self)
    window = UIWindow(windowScene: windowScene)

    reactNativeFactory?.startReactNative(
      withModuleName: "YourApp",
      in: window,
      connectionOptions: connectionOptions
    )
  }

  func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    RCTLinkingManager.scene(scene, openURLContexts: URLContexts)
  }

  func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    RCTLinkingManager.scene(scene, continue: userActivity)
  }

  override func bundleURL() -> URL? {
    #if DEBUG
    RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
    #else
    Bundle.main.url(forResource: "main", withExtension: "jsbundle")
    #endif
  }
}

The template version also sets an RCTDevMenuConfiguration in debug builds; copy it from the diff if you want the shake gesture and keyboard shortcuts. And Info.plist gets the scene manifest:

<key>UIApplicationSceneManifest</key>
<dict>
  <key>UIApplicationSupportsMultipleScenes</key>
  <false/>
  <key>UISceneConfigurations</key>
  <dict>
    <key>UIWindowSceneSessionRoleApplication</key>
    <array>
      <dict>
        <key>UISceneConfigurationName</key>
        <string>Default Configuration</string>
        <key>UISceneDelegateClassName</key>
        <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
      </dict>
    </array>
  </dict>
</dict>

Keep UIApplicationSupportsMultipleScenes at false; 0.88 supports a single scene. The part that bites real apps is everything you had added to AppDelegate over the years: deep link handlers, push notification registration, SDK initialisers that grab window. URL and user-activity callbacks now arrive on the scene, so a deep link handler left in application(_:open:options:) silently stops firing. Grep your AppDelegate for window and open url before you delete anything. Libraries that patch the AppDelegate need SceneDelegate support too; Hot Updater, for example, added a doctor check for it this month.

Applying the diff with git instead of by hand

For small diffs, by hand is fine. For bigger jumps you can pull the raw diff from rn-diff-purge and let git do the mechanical part:

curl -sL https://raw.githubusercontent.com/react-native-community/rn-diff-purge/diffs/diffs/0.87.0..0.88.0.diff \
  -o /tmp/rn.diff

# rename the template app to yours, then strip the a/RnDiffApp/ prefix
sed -i '' 's/RnDiffApp/YourApp/g; s/rndiffapp/yourapp/g' /tmp/rn.diff
git apply -p2 --reject /tmp/rn.diff

Anything that does not apply cleanly lands in a .rej file next to the original. Resolve those by hand, delete the .rej files, and never apply the project.pbxproj hunks this way; add files through Xcode instead. On Linux, drop the '' after sed -i.

Upgrading an Expo project

Expo projects do not use the Upgrade Helper. React Native versions arrive with Expo SDK releases, and the upgrade walkthrough boils down to three commands:

npm install expo@^58.0.0
npx expo install --fix
npx expo-doctor

expo install --fix aligns every Expo-managed package and the React Native version with the SDK; expo-doctor flags the rest. If you use Continuous Native Generation, delete the ios and android folders and let the next npx expo run:ios, prebuild or EAS Build regenerate them; the SceneDelegate change above then arrives for free. If you keep native folders in git, run npx pod-install and apply the native changes yourself. Expo also recommends one SDK at a time, which matches my own rule. Not sure which kind of project you have? Our Expo explainer covers the difference.

When the build still fails: the cache routine

Most post-upgrade build failures are stale caches, not real incompatibilities. In order of escalation:

# JavaScript side
watchman watch-del-all
rm -rf node_modules && npm install
npx react-native start --reset-cache

# iOS
rm -rf ~/Library/Developer/Xcode/DerivedData
cd ios && bundle exec pod deintegrate && bundle exec pod install

# Android
cd android && ./gradlew clean

npx react-native clean wraps several of these, and the react-native-clean-project package is what the official docs recommend. If Android fails with a Kotlin or AGP version error, compare the Gradle hunks in the diff again; that is almost always a version you did not bump. If the app builds and then crashes on launch with nothing in Metro, read adb logcat or the Xcode console before anything else, because native crashes never reach the JavaScript logs.

How often to upgrade

React Native supports the latest three minor versions, and a new minor lands roughly every two months. Fall four versions behind and you are on an unsupported release with no security patches, which is the scenario our security guide is written to prevent. The 0.86, 0.87 and 0.88 releases all shipped without user-facing breaking changes, so the cost of staying current has never been lower. My rule: upgrade within one month of each minor release, schedule it like a feature, and keep the hop to a single version. The forty-minute upgrades all came from following that rule; the three-day one came from ignoring it.

FAQ

What is the React Native Upgrade Helper?

A web tool that shows the full diff between the React Native app templates of any two versions, generated from the rn-diff-purge repository, with comments on why files changed. You apply those changes to your own project.

Does npx react-native upgrade still work?

No. The current React Native community CLI has no upgrade command. Use the Upgrade Helper for React Native CLI projects and npx expo install --fix for Expo projects.

Can I skip React Native versions when upgrading?

You can, and the Upgrade Helper will show a combined diff. For more than two minors, hopping one version at a time and building after each hop makes failures far easier to trace.

How do I upgrade React Native in an Expo project?

Install the next SDK with npm install expo@^58.0.0, run npx expo install --fix, then npx expo-doctor. With Continuous Native Generation, delete the native folders and rebuild.

Why does my app not launch after building with Xcode 27?

From the iOS 27 SDK, Apple requires the UIScene lifecycle. Upgrade to React Native 0.88 and move your entry point to a SceneDelegate, or adopt the SceneDelegate pattern from the 0.88 template.

Leave a Reply 0

Your email address will not be published. Required fields are marked *