How to upgrade React Native in 2026: a playbook that actually works
A React Native upgrade is four jobs. Audit. Plan. Execute. Verify.
Most failures aren't failures of execution. They're failures of audit and planning — someone skipped to the commands before they knew what they were upgrading.
Do it in version-jump-sized branches. Staircase, don't leap. Verify on physical devices, not just CI.
Most React Native upgrade guides tell you to open the upgrade helper, diff the config, and run npm install. That works for apps that are one version behind and use a tutorial-level set of dependencies.
It doesn't work for real codebases.
Real codebases are 3–18 months behind. They have custom native modules. They pull in libraries whose maintainers haven't touched them in a year. The lockfile has 1,200 entries. The Xcode project has twelve targets nobody remembers configuring.
This is a playbook for those upgrades. The ones that actually break.
An upgrade is four jobs, not one
The most common mistake is treating an upgrade as a single task. "Upgrade React Native" goes on a sprint board, a senior engineer picks it up, and two weeks later they're in a branch with 300 files changed and no clear path back.
The shape of the work is actually four phases:
- Audit. What's in the codebase? What are we upgrading?
- Plan. In what order do we do this? What's the staircase?
- Execute. What breaks, and how do we fix it in sequence?
- Verify. How do we know it didn't silently regress?
Most upgrades that fail, fail in phase 1 or phase 2. Execution gets all the attention because it's the part with commits. But if you skip the first two phases, execution is just you discovering the unknowns live.
Audit
Before touching a line of code, inventory everything the upgrade will touch. This isn't optional. This is where the surprises live.
Direct dependencies
Run npm ls --depth=0 or yarn list --depth 0. For each direct dependency, answer:
- Is it still maintained? When was the last release?
- Does it support your target React Native version?
- Does it support the New Architecture?
- Is it pinned, or on a caret range?
- How many open issues tagged with your target version?
Transitive dependencies
Your lockfile is the actual universe. package.json lists the ones you opted into. The lockfile lists the 1,200 you didn't. Run npm audit. Count critical and high. That's your CVE surface.
Native modules
Open ios/Podfile and android/build.gradle. Anything pinned to a specific version. Anything with a custom build phase. Anything that references a legacy Android API level. Any CocoaPods pinned to source repos instead of registry. All of it matters.
Build configuration
Xcode project file, Gradle config, Metro config, Babel config, TypeScript config. Each one has defaults that React Native expects. Each one probably has customizations your team added that aren't in the template.
CI
What runs on every PR? What breaks when node version changes? Which caches will go stale? Does your CI runner have the right Xcode version for your target RN?
Output of Phase 1: a single spreadsheet with one row per thing the upgrade touches. Columns for current version, target version, compat notes, known issues, effort estimate. You'll reference this document every day for the next three weeks.
Plan
You don't go from 0.68 to 0.76 in one jump. You staircase.
The version path
Minor-by-minor. Sometimes patch-by-patch if a release introduced a known regression. For each hop, identify:
- The minimum dependency bumps required to be on that version
- Any mandatory native config changes (iOS deployment target, Android SDK level)
- Whether Hermes state changes at this hop
- Whether the New Architecture flag flips
The hard decisions, up front
Decide these before you start writing code. Not during.
- Hermes. If you're still on JSC, now is the time. Going from pre-Hermes to Hermes while also bumping three RN versions triples the debugging surface.
- New Architecture. Fabric and TurboModules. Know which of your libraries are compatible. Some popular ones still aren't — decide whether to migrate off or wait. Don't discover this in the middle of a merge.
- Expo SDK. If you use Expo, the SDK version is coupled to your React Native version. Pick the target SDK before the RN target.
- Deployment target. Dropping iOS 13 support might be the real reason this upgrade is blocked. Decide now, not during App Store submission.
The order of operations
Infrastructure first — React Native, Expo SDK, Hermes. Then foundation libraries — react-native-screens, react-native-gesture-handler, react-native-reanimated. Then feature libraries — navigation, Firebase, storage, network. App code last.
Upgrading leaf dependencies before the infrastructure underneath them is a reliable way to get stuck. The leaves will reject your work.
Execute
One branch per version jump. Not one branch for the whole upgrade.
The discipline
- Freeze main for the upgrade window if you can. If you can't, rebase obsessively. Nothing eats an upgrade like merge conflicts.
- Upgrade tooling first. Run the upgrade helper for each hop. Apply its diff as a starting point, not a finishing point. It catches maybe 70% of what needs to change.
- Bump dependencies in the planned order. Don't get clever. Don't batch. One at a time, verify build, commit.
- Delete Dependabot PRs during the upgrade window. They'll add chaos. Re-enable after.
The libraries that break most
If you're going to spend extra time on any library, it's these:
react-native-reanimated— native Babel plugin, worklet semantics, deep RN internalsreact-native-gesture-handler— native setup changes frequently, RN version-sensitivereact-native-screens— coupled to navigation version and RN version simultaneouslyreact-native-firebase— large native footprint, sensitive to Gradle and Cocoapods changesreact-native-webview— platform-specific native changes every few versions
Verify native builds after every step
npm install passing doesn't mean it builds. tsc passing doesn't mean it builds. The only thing that tells you the native build works is a successful xcodebuild and gradle assembleDebug.
Run both after every non-trivial bump. If either fails, stop. Fix it before moving on. Don't accumulate failures and try to untangle them at the end.
Verify
Tests passing doesn't mean it works. This is where most teams stop and ship anyway.
Core flow verification, on physical devices
Simulator-only verification is a trap. Simulators skip whole classes of bugs — audio, camera, biometrics, background modes, anything that talks to real hardware. Exercise the core flows on at least one iPhone and one Android device.
Core flows means: cold start, login, the three features that 80% of your users use, log out, background, resume, deep link entry.
Performance regressions
Capture these before you start:
- Cold start time (launch to first meaningful render)
- Bundle size (
.ipaand.apk, separately) - JavaScript bundle size
- Memory footprint at steady state
- Crash-free rate in production or staging
Compare after the upgrade. If any of these got meaningfully worse, that's a signal, not a shrug.
TestFlight and Internal Track before production
Always. Even if you're confident. Ship the upgraded build to a beta channel for at least a week before promoting. The bugs you didn't find in QA will surface in TestFlight.
Build passes on both platforms with no warnings you can't explain. Core flows pass on physical devices. Cold start time, bundle size, and crash-free rate are within tolerance of the pre-upgrade baseline. Anything less is "we ran out of time," not "done."
Common failure modes
The mistakes that eat upgrades, in rough order of frequency:
- Skipping audit. Starts with "the upgrade helper output looks manageable" and ends with discovering an abandoned native module three weeks in.
- Merging the upgrade branch to main early. Half the team starts building on partial upgrade state. Every subsequent merge is a landmine.
- Batching version jumps. "Let's go straight from 0.68 to 0.76." Every breaking change compounds with every other breaking change. Debugging this is a career highlight in the wrong direction.
- Trusting the upgrade helper's output fully. It's a starting point. Your app isn't the template it was generated from.
- Leaving Dependabot auto-merge on during the upgrade. Concurrent dependency changes aren't compatible with an upgrade branch.
- Simulator-only verification. See above. Physical devices find what simulators don't.
- Shipping on "tests pass." Tests verify code correctness. They don't verify the build didn't regress cold start by 400ms, or that camera still works on Android 9.
When to DIY, when not to
DIY if:
- One minor version jump. Healthy codebase. Active maintainers on your critical dependencies.
- You have a senior engineer who can own it full-time for two to three weeks.
- No active App Store deadline or compliance milestone riding on the outcome.
Don't DIY if:
- You're three or more major React Native versions behind.
- A previous upgrade attempt already failed. The second attempt rarely goes better than the first without outside pattern-matching.
- You're on deadline — an App Store OS requirement, a SOC 2 audit, an enterprise customer security review.
- Your senior engineers are already on the critical path for feature work. The opportunity cost of diverting them usually dominates the contracting cost.
A multi-version upgrade on a real codebase typically runs 80–160 hours of senior engineer time. At $150–$250 per hour, that's $12,000–$40,000 in opportunity cost — plus the roadmap delay. Compare that to whatever outside option you're considering before deciding.
Upgrade tooling, quick reference
| Tool | What it does | When to use |
|---|---|---|
| react-native upgrade-helper | Web diff of template config between two RN versions | Starting point for every version hop |
| npx @react-native-community/cli upgrade | CLI equivalent of the upgrade helper | When you want a patch applied automatically |
| npm-check-updates | Lists available dependency updates | Phase 1 audit; use with --target minor or --target patch |
| npm audit / yarn audit | Reports known CVEs in the lockfile | Every phase. Especially before you ship |
| RN New Architecture compat matrix | Lists which popular libraries support Fabric and TurboModules | Phase 2 planning |
| Expo SDK release notes | Maps Expo SDK versions to supported RN versions | Phase 2, if you use Expo |
| depcheck | Finds unused dependencies | Phase 1. Fewer deps is fewer upgrade surface |
| Our free scanner | CVE + outdated package check for React Native lockfiles | Phase 1 audit. 90 seconds, no signup |
Not sure where you stand?
Run the free scanner against your lockfile. You'll get a ranked list of known CVEs and outdated packages in about 90 seconds — a useful input to Phase 1 of your own audit.
Frequently Asked Questions
How long does a React Native upgrade take?
For a single-version hop on a healthy codebase, one to three days. For a multi-version upgrade on a codebase six to eighteen months behind, 80 to 160 hours of senior engineer time is typical. The variance is driven by native module count, custom build configuration, and how aggressively the codebase used bleeding-edge libraries.
Can we skip React Native versions?
Technically yes. Practically, don't. Each version jump introduces its own breaking changes, and debugging a failure when you've skipped three intermediate versions is much harder than doing it incrementally. Upgrade one minor version at a time, verify, then move to the next.
What's the difference between upgrading React Native and upgrading dependencies?
They're coupled but distinct. React Native's version sets a ceiling on what many of your native-adjacent dependencies can do. But a lot of your upgrade work is in the packages on top of React Native — gesture handler, reanimated, screens, Firebase, storage, navigation. You'll often do more work updating those than on the React Native version itself.
What order should we upgrade dependencies in?
Infrastructure first — React Native, Expo SDK if used, Hermes. Then foundational libraries that everything else depends on — react-native-screens, react-native-gesture-handler. Then feature libraries — navigation, Firebase, storage, network. App code last. Trying to upgrade leaf dependencies before the infrastructure underneath them is a reliable way to get stuck.
How do we know when the upgrade is done?
Build passes on both platforms with no warnings you can't explain. Core user flows pass on physical devices, not just simulators. Cold start time and bundle size are within tolerance of the pre-upgrade baseline. Crash-free rate in TestFlight or Internal Track matches production. If any of these are worse, you're not done — you're at the part where people usually stop and ship anyway.