Skip to content
Mario Norato
← All posts

Upgrading React Native 0.67 to 0.73 while the release train keeps moving

Six minor versions, a financial app in the App Store, and no feature freeze. Here is the strategy that made it survivable.

3 min read

This post is also available in Italian — Aggiornare React Native da 0.67 a 0.73 senza fermare le release

There is a version of this post that is a list of npm install commands. This is not that post, because the commands were never the hard part.

The situation: a consumer Bitcoin application, live in both stores, on React Native 0.67. Six minor versions behind. Nobody was going to grant a feature freeze for a version bump, and rightly so — the app made money and the roadmap was full.

Why you cannot jump straight to the target

The obvious plan is to go from 0.67 to 0.73 in one branch. It fails for a reason that is easy to underestimate: when the app breaks, you have no idea which of the six versions broke it.

Every minor release of React Native moves something structural. Between 0.67 and 0.73 you cross Hermes becoming the default engine, changes to the Android Gradle plugin, a new Metro resolver, and a long tail of native dependency churn. Land them together and every bug is a bisect problem across six release notes at once.

So: one minor version per branch, each one merged and shipped to internal testers before the next started.

# One version at a time, and read every line of the diff
npx react-native upgrade 0.68.0

The React Native Upgrade Helper gives you the diff between any two versions of the template project. Treat it as the source of truth over the codemod, especially for the native files — android/app/build.gradle and the iOS Podfile almost always need a human.

The dependency audit comes first

Before touching the framework version, I went through package.json and answered one question per line: does this library have a release that supports the version I am moving to?

Three categories came out of it:

  1. Actively maintained — bump alongside React Native, no drama.
  2. Maintained but behind — pin, and check the issue tracker before each step.
  3. Abandoned — replace before the migration, not during.

That third category is where the schedule actually goes. A dead native module discovered halfway through a version bump means you are now doing two migrations in one branch, which is the thing this whole approach exists to avoid.

Tests are what made it a non-event

The coverage work had happened earlier, for unrelated reasons: the suite sat around 90%, with Detox covering the flows that touch money. That turned out to be the thing that made a six-version upgrade routine rather than terrifying.

A framework upgrade breaks code in places nobody thinks to click. Number formatting shifts. A FlatList stops scrolling on one Android API level. A date renders a day off in one timezone. You do not find these by opening the app and looking at it. You find them because a test that has passed for two years suddenly does not.

If your coverage is thin, spend the time there first. An upgrade with no safety net is not a migration, it is a bet.

What I would do differently

Upgrade continuously. The reason 0.67 to 0.73 was a project is that nobody had treated the framework version as maintenance for eighteen months. One minor version, every couple of months, absorbed into ordinary sprint work, never becomes a project at all.

The cost of being current is small and constant. The cost of catching up is large and lands all at once, usually at the worst possible moment.