React Native is easy to misunderstand if you come from web development.
You write React components. You use JavaScript or TypeScript. You run a dev server. You get fast refresh. The workflow feels close enough to React on the web that it is tempting to think of React Native as a web app wrapped in a mobile shell.
That mental model is wrong.
React Native builds native mobile apps. The UI is not rendered into a browser DOM. There are no div elements. There is no hidden web page pretending to be an app. Your React code describes a native interface, and React Native turns that description into real iOS and Android components.
Expo then builds on top of React Native by smoothing out the rough edges: native configuration, development builds, routing, over-the-air updates, cloud builds, and access to common device APIs.
Once you understand the boundary between the native runtime and the JavaScript bundle, the whole ecosystem becomes much less confusing.
React Native Is Not React DOM
React for the web renders to the browser DOM. React Native renders to native UI primitives.
| React web | React Native |
|---|---|
div | View |
span or p | Text |
img | Image |
| Browser DOM | Native iOS and Android views |
When you write this:
<View style={{ backgroundColor: "red" }}>
<Text>Hello</Text>
</View>React Native does not create HTML. On iOS, it creates native UIKit views. On Android, it creates native Android views.
That distinction matters for performance, styling, accessibility, gestures, native modules, and deployment. You are not shipping a website. You are shipping a native app whose behavior is driven by JavaScript.
The Two Programs Inside Every React Native App
The most important React Native concept is that your app is really two programs working together.
Your mobile app
Native runtime <-> JavaScript bundleThe native runtime is the compiled app. It is written in platform-native languages and built with native tooling. On iOS, that means Xcode, Swift, and Objective-C. On Android, that means Gradle, Kotlin, and Java.
The JavaScript bundle is your React application code. It contains your screens, components, navigation, state management, and business logic.
The native runtime contains the JavaScript engine, native modules, permissions, metadata, app icons, splash screens, and the bridge between JavaScript and native APIs. It is compiled, signed, platform-specific, and cannot be changed on a user's device without a native update.
The JavaScript bundle is more flexible. During development, it can be loaded from your computer. In production, it is bundled into the app. With over-the-air updates, it can sometimes be updated without app store review.
The browser analogy helps here. The native runtime is like the browser. The JavaScript bundle is like the website. You can update a website without updating Chrome, but if you need a browser feature Chrome does not have, the browser itself must change.
What Actually Requires a Native Rebuild
This split explains one of the most common sources of confusion: why some changes update instantly and others require a rebuild.
| Change | Native rebuild needed? | OTA update possible? |
|---|---|---|
| Fix UI copy | No | Yes |
| Add a new screen | No | Yes |
| Fix business logic | No | Yes |
| Add a JavaScript-only library | No | Yes |
| Add a native library | Yes | No |
| Change app icon | Yes | No |
| Change permissions | Yes | No |
If the change only affects JavaScript, it can usually be refreshed quickly and may be eligible for an OTA update. If the change affects native capabilities, permissions, binary metadata, or native modules, you need a new build.
That rule explains most development workflow decisions in React Native.
What Expo Adds
Expo is a platform built on top of React Native. It gives you a more complete development environment and hides much of the native build complexity until you need it.
In an Expo managed project, you often do not see ios/ and android/ folders. Instead, you configure native behavior through app.json, app.config.js, config plugins, and installed packages. When it is time to build, Expo can generate the native projects for you.
npx expo prebuildThat command creates the native ios/ and android/ projects based on your configuration and dependencies.
Expo also provides common native modules, development tooling, file-based routing through Expo Router, EAS Build for cloud builds, EAS Submit for app store submission, and EAS Update for over-the-air JavaScript updates.
The point is not that Expo removes native code from existence. It manages a lot of it for you.
Expo Go vs Development Builds
Expo Go is a prebuilt app maintained by the Expo team. You install it from the app store, run npx expo start, scan a QR code, and Expo Go loads your JavaScript bundle.
It is excellent for learning and prototyping. You do not need to build anything before seeing your app on a phone.
The limitation is that Expo Go only contains the native modules already included by Expo. You cannot test every possible native dependency, custom app icon behavior, production splash screen behavior, universal links, or remote push notification setup as your actual app.
A development build is different. It is your own app binary, built for development, with expo-dev-client installed. You can think of it as your custom version of Expo Go. It includes the native modules your app actually needs.
| Feature | Expo Go | Development build |
|---|---|---|
| Setup | Install from app store | Build your own app |
| Native libraries | Limited to Expo Go's included modules | Any native library you include |
| App icon and splash | Not your final app | Your real app configuration |
| Push notifications | Limited | Realistic testing |
| Best use | Learning and prototypes | Serious app development |
The practical rule is simple. Start with Expo Go if it is enough. Move to development builds when your app needs native behavior that Expo Go cannot provide.
Development and Production Load JavaScript Differently
In development, your phone usually loads the JavaScript bundle from Metro, the local development server running on your computer.
Phone running Expo Go or a dev build
|
| requests JavaScript bundle
v
Metro dev server on your computerThat is why your phone and computer often need to be on the same network. It is also why fast refresh works. The app is pulling code from a live development server.
In production, the JavaScript bundle is packaged inside the app binary.
YourApp.apk or YourApp.ipa
native code
assets
main.jsbundleThe app does not need your computer. It can start offline. The JavaScript is optimized and bundled for production.
EAS Update adds a hybrid model. The app ships with a bundled JavaScript version, but it can check for newer JavaScript updates and cache them on the device. This is powerful for bug fixes and UI updates, but it has a hard boundary: OTA updates cannot add new native capabilities.
The Shape of an Expo Project
An Expo managed project usually separates JavaScript code, native configuration, assets, and build configuration clearly.
your-project/
app/ screens and routes
components/ reusable UI
hooks/ custom hooks
constants/ shared values
assets/ images and fonts
app.json native app configuration
package.json dependencies
eas.json EAS build and update configThe app.json file controls native app metadata.
{
"expo": {
"name": "My App",
"slug": "my-app",
"icon": "./assets/icon.png",
"android": {
"package": "com.company.myapp"
},
"ios": {
"bundleIdentifier": "com.company.myapp"
}
}
}The package.json file matters because dependencies can include native modules. Installing a JavaScript-only package and installing a package with native code are not equivalent.
React Native Is Still Frontend Code
React Native can make a mobile app feel native, but it does not turn frontend code into backend code.
A React Native app should not connect directly to a production database. It should not contain database credentials. It should not store private API secrets. APK and IPA files can be inspected, and JavaScript bundles can be extracted.
The correct architecture is the same as most frontend systems.
Mobile app <-> Backend API <-> DatabaseThe mobile app talks to your API over HTTPS. Your API handles authentication, authorization, validation, rate limiting, and database access. The database remains private.
Exposing an API URL is normal. Users can see network traffic from their own device. Security does not come from hiding the URL. It comes from authentication, authorization, and server-side enforcement.
Backend-as-a-service tools such as Supabase and Firebase still follow this pattern. The client talks to an API layer, not directly to raw database credentials with unlimited access.
For example, Supabase's public anon key is designed to be exposed, but it must be paired with proper authentication and row-level security.
const supabase = createClient(
"https://xyz.supabase.co",
"public-anon-key"
);
const { data } = await supabase
.from("interviews")
.select("*");The safety comes from the policies behind the API, not from the key being secret.
The Mental Model to Keep
React Native is a native container running a JavaScript app. Expo is the platform that makes that container easier to configure, build, update, and ship.
Expo Go is a shared prebuilt container. A development build is your custom container. An OTA update changes the JavaScript running inside the container, not the container itself.
Once you see that boundary, the ecosystem makes much more sense. You know why adding a screen is instant, why adding a native library requires a build, why Expo Go eventually stops being enough, and why mobile apps still need a backend.
That is the React Native and Expo learning curve in one sentence: understand what lives in JavaScript, what lives in native code, and what crosses the boundary between them.
References
Read more
Architecting Reliable Mobile Billing: What I Learned the Hard Way
A real-world look at fixing mobile subscription billing when webhooks, sandbox purchases, and user identity break down.
OAuth vs OIDC: The Difference Finally Explained
A practical explanation of OAuth, OIDC, access tokens, and ID tokens without the usual authentication confusion.
Coder Tasks, Workspaces, and OpenCode: A Practical Mental Model
Build a clearer mental model for Coder workspaces, templates, provisioners, and AI coding tasks.
