Pushing a React Native fix through review without waiting on stores
A field supervisor at a logistics client reports that the dispatch confirmation button freezes on the list view. The build that shipped last week has the bug. The supervisor's phone is a Rs. 8,000 Lava device on a 4G connection that drops twice an hour. He cannot reinstall, cannot clear cache, and the Play Store update is 48 hours away.
We pushed a fix through CodePush in 11 minutes. The supervisor pulled the update on the second tap.
OTA updates for React Native work because the framework bundles JavaScript into an asset file, and the native shell loads it on launch. You swap the bundle, the native binary stays the same, and you skip the review queues entirely. Apple permits it under Developer Guideline 4.7, with one specific constraint: you cannot change the app's primary purpose or add new features. Bugfixes, layout adjustments, and copy changes are fine. Adding a payment screen to an app that didn't have one is not. Pushing a new feature flag that enables a module already in the binary is a gray area we avoid.
What you can push reliably:
- JS logic changes, state handling, form validation, navigation
- Component layout, styling, text content
- Asset swaps within reason — small images, icons
What you cannot change without a store release:
- Native dependencies. Adding or removing a library that requires `react-native link` or a Podfile change means a new build. OTA cannot touch native code.
- Permission strings in Info.plist or AndroidManifest.xml. Adding a camera permission requires review.
- App icon, launch screen, or store metadata.
- Any change that requires a new entitlement or signing configuration.
There's a quieter limit worth knowing. OTA updates don't help if the bug is in native code. A crash in the camera library or a permission dialog timing out is invisible to your JS bundle. If the crash prevents the app from loading the JS bundle at all, the update can't reach the device. We had one case where a native crash loop meant 40% of the fleet couldn't download the fix. We built a kill-switch into the native shell after that — a small HTTP call on launch that can disable features or force a store update before the JS bundle loads.
We track the bundle version, not just the store version. Every release we ship through the stores includes the current JS bundle. OTA patches sit on top of that base. If a user skips ten OTA updates and then downloads a store update, the store version overwrites everything and OTA catches up incrementally from there. We've seen this cause confusion when a client reports a bug in version 2.4 and the OTA log shows 2.4-patch3. We log both at the top of the debug screen now.
The tradeoff: OTA is fast and cheap and lets you fix issues without the 48-hour review window. It doesn't replace store releases, and it shouldn't carry new features. We use it for bug fixes and configuration changes, and we push a store release every four to six weeks regardless. If the next store release is already in review, we wait. OTA is not a replacement for the regular cadence — it fills the gap between when you find the bug and when the store catches up.