Every React Native team upgrades eventually, and the question before it is always the same: what is going to break in our code? The release notes list the changes, but they don't tell you which of them touch your app.
So we ran that question over 947 open-source React Native and Expo apps from GitHub, with NativeKeel's new upgrade command. It reads each project's code and dependencies, matches them against the breaking changes between its version and the newest one (React Native 0.88 / Expo SDK 58), and returns a verdict with file and line.
The result
| Verdict | Apps | Share |
|---|---|---|
| Already on the newest line | 36 | 4% |
| Ready: no breaking change found in their code | 226 | 24% |
| Several versions behind, nothing found in the newest steps | 338 | 36% |
| Not ready: at least one upgrade blocker | 347 | 37% |
For 345 of those 347 apps (36% of all), the blocker is in code they wrote themselves, before counting native build issues.
What breaks most
| Breaking change | Apps |
|---|---|
StyleSheet.absoluteFillObject removed (React Native 0.85) |
143 |
StatusBar backgroundColor / translucent props removed (0.87) |
140 |
expo-file-system: File.write() is now async (Expo SDK 58) |
65 |
InteractionManager removed (0.87) |
64 |
| expo-router navigation core reworked (Expo SDK 58) | 37 |
Deep imports from react-native/Libraries/ are type errors (0.87) |
36 |
Two of these are worth knowing about even if you never run a tool.
absoluteFillObject fails silently. React Native 0.85 removed it without a warning. TypeScript stops the build, but in JavaScript { ...StyleSheet.absoluteFillObject, top: 10 } spreads undefined, the style loses position: 'absolute', and overlays stop covering their parent. You find out on screen. The fix is a rename to StyleSheet.absoluteFill, which is now a plain object you can spread (details).
InteractionManager crashes in release builds. A development build shows a clear red screen; a release build only has undefined and fails at the first runAfterInteractions call. Move to requestIdleCallback (details).
Not every release breaks things
We checked each React Native release from 0.82 to 0.88 against its official notes. 0.83, 0.86 and 0.88 have no user-facing breaking changes; the code-level breaks cluster in 0.85 and 0.87. If you are on 0.86, the move to 0.88 is mostly 0.87's list.
Being honest about what a static check can't see
- The verdict covers the breaking changes in the catalog, which is checked against the official release notes for each version. For apps several versions behind, the earliest steps are not fully covered, so those get "several versions behind" instead of a green light. Upgrade one step at a time.
- Native build problems (Gradle, CocoaPods, a library that hasn't been updated) need an actual build. The tool flags the ones it knows (for example libraries whose iOS headers stop compiling on 0.87), but it doesn't replace building on the target version.
Try it on your app
npx nativekeel upgrade
It runs locally, sends no code anywhere, and exits with code 1 when blockers remain, so you can put it in the pull request that bumps React Native. There is also an MCP tool (upgrade_readiness) if you work with an AI agent.
The scan used 1,162 public GitHub repositories with React Native or Expo; 213 were monorepo roots or libraries rather than a single app and are left out, as are 2 whose version could not be read.
Top comments (5)
The absoluteFillObject one is nasty, since in plain JS the spread is just undefined and the overlay quietly stops covering. Did many apps hit InteractionManager inside third-party libraries, where renaming your own calls isn't enough?
A quick way to answer that for your own app: grep node_modules for InteractionManager (the lockfile only lists packages, not usage) and check each hit's owner for a release that stops calling it. If a package is unmaintained, patch-package or a maintained fork is safer than renaming your own calls and assuming the crash is gone. Then smoke-test the affected path in a release build, since per the article dev builds show a red screen while release just fails at the first runAfterInteractions call.
Grepping node_modules for actual calls is the right move, the lockfile can't tell you who still uses it. And a release-build smoke test is the only place that one would show up, so that's a good last step.
Yes, quite a few. On the same corpus, 58 of 900 apps call InteractionManager through a library (compared with 64 in their own code). The most common: react-native-modal (25 apps; every release from 11.10.0 to 14.0.0 calls it), react-native-draggable-flatlist 4.0.3 (18) and the deprecated react-navigation-stack v2 (8).
Agreed on the approach: check each hit for a release that drops the call, and if there is none, patch-package or a maintained fork. npx nativekeel upgrade now does that lookup in node_modules too: a library that still calls a removed API shows up as a blocker with the package, the version and the file, so you don't have to grep by hand.
Shipped in 0.1.129: NativeKeel now flags a SceneDelegate that starts React Native without the connection options. Thanks again for the idea!