What React Native suits, and what it does not
React Native lets you write an app in TypeScript with React's component model, and it renders with the platform's own native views, so a button on iOS is an iOS button. It is one of the main routes to a cross-platform app.
It fits well when:
- Your team knows React and TypeScript, so people can move between web and mobile work.
- You share code with a web product, such as one built with Next.js: types, validation rules and API clients.
- The app is made of screens, forms, lists and feeds backed by an API.
It fits less well when:
- The app is graphics-heavy, such as a game, real-time video or advanced AR. That belongs in native code or a game engine.
- Most screens need custom native views or deep platform features. The more native code, the less you save. See native iOS and Android development.
- You want one fully custom, pixel-identical interface on both platforms. Flutter draws its own interface and can be easier for that.
For a side-by-side view, read Flutter vs React Native.
Expo or bare React Native
Expo is a framework and toolset built on React Native. It handles project setup, navigation conventions, builds and updates. In a bare workflow you manage the native iOS and Android projects yourself. The React Native documentation recommends starting new projects with a framework such as Expo, and for most apps that is the sensible default.
| Aspect | Expo | Bare React Native |
|---|---|---|
| Native projects | Generated from configuration | Edited directly in Xcode and Android Studio |
| Custom native code | Development builds, config plugins and the Expo Modules API | Full freedom and full responsibility |
| Builds and updates | Cloud build and update services are available | You set up and maintain your own pipeline |
| Best for | Most new apps | Unusual native needs or an existing native codebase |
A common worry is that Expo locks you in. Its standard preview app, Expo Go, cannot run your own native modules, but a development build can, and you can always move to managing the native projects yourself.
Native modules when JavaScript is not enough
Sooner or later an app needs something React Native does not provide: a vendor SDK, a Bluetooth accessory, a custom camera view. You add it as a native module in Swift or Kotlin, exposed to TypeScript, or as a native view. Modern React Native calls native code directly, which removes much of the overhead of its older asynchronous bridge.
Our approach is to keep the native layer thin, put a typed TypeScript interface in front of it, give both platforms the same behaviour and test on real devices. Before adopting a third-party library we check that it is maintained, supports the current architecture, works with Expo and does not add permissions you do not need.
Performance considerations
React Native has two threads to protect: the JavaScript thread, where your logic and React rendering run, and the UI thread, where the screen is drawn. Jank appears when either is overloaded. The common causes:
- Unnecessary re-renders. Keep state local, memoise costly components and give list rows stable keys.
- Long lists. Use virtualised list components, not a scroll view holding hundreds of rows.
- Animation on the JavaScript thread. Use a library that runs animations on the UI thread, and animate transforms and opacity.
- Heavy computation. Move it to native code, a background task or the server.
- Slow start-up. Load screens lazily, trim dependencies and use the Hermes engine, which runs precompiled bytecode.
Measure on a real mid-range Android phone, in a release build, with the platform profilers. Debug builds are much slower and say little about production.
Over-the-air updates and their limits
A React Native app can fetch a new JavaScript bundle and assets when it starts, so a fix reaches users without a store release. Expo's update service is one option, and you can host your own. The limits matter as much as the feature:
- Only JavaScript and assets change. A new native module, permission, SDK or React Native version needs a new binary through the store.
- Store rules apply. Apple and Google allow updates to interpreted code, but not changes that alter what the app is or does after review. Read each store's current policy before using updates for new features.
- Versions must match. An update must be compatible with the native build already on the device, so updates are tied to a runtime version.
- Plan a rollback. Use staged rollouts and a quick way to revert.
- It widens the attack surface. Whoever can publish an update can change the app. Protect that account with multi-factor authentication and use signed updates where the tooling supports them.
In short, over-the-air updates are for fixes and small corrections. New features go through the stores.
Testing a React Native app
Several layers work together:
- Unit tests with Jest for logic, state and utilities.
- Component tests with React Native Testing Library, checking what a screen renders and how it responds.
- End-to-end tests with a tool such as Detox or Maestro, driving the real app through sign-in or checkout.
- Type checking and linting on every change, so TypeScript catches mistakes before a test runs.
Then test on devices: older Android phones, small screens, large fonts, slow networks and Arabic. Native modules and updates need their own test cases, because they fail in ways JavaScript tests never see.
Releasing to the App Store and Google Play
A React Native app ships as an iOS archive and an Android App Bundle, like any other app. Cloud build services can produce the iOS build without a local Mac, though you still need an Apple developer account and correct signing. Beta builds go through TestFlight and the Google Play testing tracks, and phased or staged rollouts let a bad release reach few users first. Decide which update channel each build listens to, so a test update never reaches production users, and leave a buffer for store review.
Right-to-left support
React Native supports right-to-left layouts, but Arabic needs deliberate setup. Enable RTL in the app configuration and read the direction from the device language. Switching direction inside the app generally needs a restart, so plan for that if you offer a language switch.
- Style with start and end, such as marginStart and paddingEnd, not left and right. React Native flips left and right automatically in RTL, which confuses teams that mix both.
- Mirror directional icons yourself. Images, logos and many animations are not flipped for you.
- Keep strings out of components, and format numbers and dates by locale.
- Check the Arabic font on both platforms, and test forms, keyboards and mixed Arabic and English text.
See Arabic and RTL design for the design side.
Mobile security for React Native apps
The JavaScript of a React Native app ships inside the app and can be extracted and read. Every project starts with a threat review. For this stack we focus on:
- Secure storage. AsyncStorage is not encrypted. Use a library that keeps values in the iOS Keychain and the Android Keystore.
- No secrets in the bundle. Precompiled bytecode is not encryption. Keep real keys on the server.
- Certificate pinning. Pin your API's public key, with a backup and a rotation plan.
- Obfuscation. Minification and bytecode make reading harder, but treat them as a speed bump.
- Rooted and jailbroken devices. Detection can be bypassed, so treat it as one signal and keep critical checks on the server.
- Dependencies. A large tree of third-party packages is a supply-chain risk. Pin versions, review updates and run dependency scanning.
Our cybersecurity service tests mobile apps before release, and the guide to secure app development covers these practices in more detail.
Before you commit: list the native features your product needs and check how many have a maintained React Native library. If many do not, compare the cost with native development.