Obi Madu's Blog
Back to all articles
MobileSystem DesignInfrastructureFrontend

React Native and Expo: The Mental Model That Makes It Click

Understand how React Native and Expo really work, from native builds to OTA updates and development workflows.

React Native and Expo: The Mental Model That Makes It Click

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 webReact Native
divView
span or pText
imgImage
Browser DOMNative 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 bundle

The 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.

ChangeNative rebuild needed?OTA update possible?
Fix UI copyNoYes
Add a new screenNoYes
Fix business logicNoYes
Add a JavaScript-only libraryNoYes
Add a native libraryYesNo
Change app iconYesNo
Change permissionsYesNo

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 prebuild

That 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.

FeatureExpo GoDevelopment build
SetupInstall from app storeBuild your own app
Native librariesLimited to Expo Go's included modulesAny native library you include
App icon and splashNot your final appYour real app configuration
Push notificationsLimitedRealistic testing
Best useLearning and prototypesSerious 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 computer

That 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.jsbundle

The 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 config

The 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 <-> Database

The 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

Expo Documentation

React Native Documentation

EAS Build

EAS Update

Expo Router