How React Native Runs Your Code

Lesson 1 of 5

Why React Native Exists

"All problems in computer science can be solved by another level of indirection." — David Wheeler

React Native is the answer to a question that had two bad answers for a decade: how do you write one application that runs properly on both phones?

The bad answers were well understood. Write it twice — one Swift team, one Kotlin team, two codebases that drift, every feature specified once and built twice. Or wrap a web page in a native shell, which is what Cordova and PhoneGap did, and which produced applications that looked almost right and felt immediately wrong: scrolling with the wrong momentum, no platform gestures, buttons that were not the platform's buttons.

React Native's bet was a third thing, and Wheeler's line is exactly the shape of it. It does not draw its own buttons and it does not wrap a web view. It keeps React's programming model and changes what React renders to — the components you write become real UIViews on iOS and real Views on Android, driven from JavaScript. The indirection that makes this possible was already in React before anyone had built a phone app with it, which is the genuinely surprising part of the story.

This chapter establishes that claim by looking, not asserting. I traced React Native 0.81.4's source to find out what <View> actually is, and the answer is more literal than most descriptions suggest. Then we deal with the honest limits — including what this volume's own playground can and cannot show you, because that boundary shapes every chapter that follows.

The big question

React shipped in 2013 as a library for building web interfaces, and React Native arrived in 2015 rendering to iOS and Android views instead. Almost nothing in React itself had to change. What was already true about React's design that made a second, completely different rendering target possible at all?

Three ways to be cross-platform

There are only three strategies, and every cross-platform toolkit is one of them. Naming them is worth doing, because the trade-offs are not marketing positions — they follow from the strategy and cannot be engineered away.

Wrap a web view. Ship a browser, run a web app inside it. Cheapest to build, and it inherits every strength of the web. What it cannot inherit is the platform: the scroll physics, the keyboard behaviour, the share sheet, the way a list bounces at the end. Users do not describe these individually — they say the app "feels weird."

Draw your own pixels. Ship a rendering engine and paint every control yourself. Flutter does this, and it buys total consistency: identical output on both platforms and complete control over every frame. The cost is that you now own the entire widget set. When iOS changes how a text field behaves, you get the new behaviour when your framework implements it, not when the OS ships it.

Drive real native views from JavaScript. Keep the platform's actual widgets and control them from a JavaScript thread. You inherit platform behaviour, accessibility and OS updates for free. The cost is a boundary between two worlds, and the rest of this volume is largely about that boundary.

JavaScript — Try it Yourself
Loading editor…
Console
Runs automatically. Edit the code to see it change.

That slogan distinction is not marketing pedantry, and it predicts what the rest of this volume feels like. You will write two platforms' worth of adjustments. What you will not do is learn two component models, two state systems, or two ways to think about a list.

Everyday analogy

Think of a play staged in two cities. One approach is to write two scripts, cast two companies and rehearse twice — accurate to each venue and twice the work. Another is to build a portable stage that travels: identical every night, and it never uses the theatre's own lighting rig, so it looks slightly foreign in both cities.

React Native is the third approach: one script, one director's notes, and each venue's own stage crew and lighting. The performance is recognisably the same play, and it uses the room it is in. The cost is the one every touring production knows — the director is not standing next to the lighting board, so every cue has to be communicated across a gap. That gap is the boundary, and it is the recurring character of this volume.

React was never a DOM library

Here is the part that makes React Native possible, and it was true two years before React Native existed.

React separates two jobs. One is deciding what the interface should look like — running your components, diffing the resulting tree, working out the minimum set of changes. That is the reconciler, and it knows nothing about browsers. The other job is actually creating and updating things — and that is a host config, a set of functions like "create an instance of this type", "append this child", "update these props".

react-dom is one host config, where "create an instance" means document.createElement. React Native is another, where it means creating a native view. Same reconciler, different host.

You can verify this from the packages rather than taking my word for it. React Native 0.81.4 declares a peer dependency of react: ^19.1.0 — the same React the web uses, not a fork. And instead of react-dom it ships its own renderers in Libraries/Renderer/implementations/: ReactNativeRenderer for the old architecture and ReactFabric for the new one.

JavaScript — Try it Yourself
Loading editor…
Console
Runs automatically. Edit the code to see it change.

The reconciler in that block is a toy — the real one handles interruption, priorities, suspense and a great deal else. But the shape is right, and the shape is the point: your components are host-agnostic, and swapping the host swaps the platform.

Where this came from

React Native was announced at React.js Conf in January 2015, and the demo that convinced people was not a toy — Facebook had already shipped parts of its Groups app with it. What made the announcement land was the reframing: the industry had spent years chasing "write once, run anywhere", which repeatedly produced software that ran everywhere and belonged nowhere. Facebook proposed "learn once, write anywhere" instead, conceding up front that you would write platform-specific code and claiming only that you would not need a second mental model to do it.

The technical groundwork was older and less deliberate. React's reconciler had been separated from its DOM operations because the team wanted to test rendering logic without a browser, and that seam turned out to be the thing that made a native renderer possible. It is a good example of a boundary drawn for one reason paying off for a completely different one — and it is why react-native-web, a third target that maps the same components back onto the DOM, could exist too.

What <View> actually is

Descriptions of React Native tend to say it "renders to native components", which is true and vague. I traced it in 0.81.4 to find out how literal the claim is, and it is very literal.

View.js is a thin JavaScript wrapper — it forwards a ref and processes a few props, then renders <ViewNativeComponent>. That file contains no drawing code of any kind. ViewNativeComponent.js does one meaningful thing:

const ViewNativeComponent: HostComponent<Props> =
  NativeComponentRegistry.get<Props>('RCTView', () => ({
    uiViewClassName: 'RCTView',
  }));

That is the bottom of the JavaScript side. <View> resolves to the string 'RCTView' — a host type name, exactly as 'div' is a host type name in React DOM. The string is then resolved by the platform: React/Views/RCTView.h in the package is an Objective-C UIView subclass, ReactAndroid/.../views/view/ holds the Android ViewManager, and React/Fabric/.../RCTViewComponentView.h is the New Architecture's version.

So the parallel is exact. In React DOM, <div> is a string the host turns into an HTMLDivElement. In React Native, <View> is a string the host turns into a UIView. Your JavaScript never touches either.

React — Try it Yourself
Loading editor…
Preview

Notice what is not in that code: no div, no className, no CSS file, no px. View and Text are not aliases for HTML elements — on a device they are names that resolve to platform classes. And Text is not merely a styled box, which is the subject of the next Part: it has nesting rules a <span> does not.

The engineer's notebook

The habit worth forming from the very first chapter is to read the framework's source when a description feels vague. "Renders to native components" told me nothing actionable; forty lines of View.js and ViewNativeComponent.js told me that <View> is the string 'RCTView' and that the JavaScript side owns no drawing code at all. React Native is unusually rewarding here because the JavaScript half ships in your node_modules — you can read the whole chain in a few minutes, and doing it once permanently fixes the mental model.

The related discipline is to keep the version next to the claim. Everything in this chapter is 0.81.4, and React Native moves: the New Architecture changed the answer to "how does a prop reach a view", and a confident statement about the bridge dated 2019 is now wrong in specifics. Writing the version down is what stops a stale fact from being repeated with a fresh face — a habit the previous volume's author learned the hard way, repeatedly.

What "native" buys, and what the boundary costs

The gain from real native views is concrete and mostly invisible until you lose it. Platform widgets bring their own scroll physics, their own text selection, their own keyboard avoidance, their own accessibility tree that a screen reader already knows how to walk — and when the OS updates, they update with it, without a release from you.

The cost is that your code and your views live in different places. JavaScript runs on its own thread; the views are owned by the platform's UI thread. Everything you do to the interface is therefore a message, and messages have properties that direct function calls do not: they are asynchronous, they are batched, and their arguments must be serialisable.

Almost every performance rule in this volume descends from that one fact.

JavaScript — Try it Yourself
Loading editor…
Console
Runs automatically. Edit the code to see it change.

The batched column is the important one. Sixty operations became three crossings, and that is the difference between a usable framework and a slideshow. It also explains a class of bug that surprises people arriving from the web: you cannot read back a layout you just requested, because the request has not crossed yet.

In the real world

The most common way teams get this wrong is choosing React Native for the wrong reason. "We know React, so mobile is free" is not true and produces a bad six months. What is true is that the *programming model* transfers — components, hooks, state, effects — while the platform does not: you will still learn navigation as state rather than URLs, layout in Yoga rather than CSS, permissions, store review, and two sets of build tooling. Teams that budget for "React plus a platform" do well; teams that budget for "React" specifically do not.

The second is reaching for a web view inside the native app. It is always available, always faster to build, and the reason you chose React Native was to avoid exactly that feel. There are legitimate uses — a terms-of-service page, a third-party checkout — and there is a strong pull to use one for a real screen because the web version already exists. Users notice, and they cannot tell you why.

The third is assuming a description of the architecture is current. React Native has changed its answer to "how does a prop reach a view" once already, and blog posts about the bridge outnumber blog posts about JSI by years of accumulation. This is a framework where reading the source in your own node_modules beats reading an article, because the source is the version you actually have.

What this volume can and cannot show you

One more thing before we go further, and it is unusual for this Library to have to say it.

Every previous volume has held to a simple promise: if there is a code block, you can run it, and what you see is true. Volume 19 cannot keep that promise everywhere, because the subject is a device and the playground is a browser. Rather than paper over that, here is the map — and this chapter's own rn try block above was already running under the first of these caveats.

rn try blocks run react-native-web. They are honest about structure: View, Text, Pressable, StyleSheet compose exactly as they do on a device, and flexbox layout applies. They are not honest about the platform, and the next block shows you the seam rather than describing it.

Expo Snack embeds appear where the platform genuinely matters — real iOS and Android simulators, and a QR code so you can run the code on your own phone. That is where anything platform-specific, gesture-driven, or native-module-shaped will live.

Miniatures — models like the reconciler and the boundary above — carry the internals. A navigation stack is a data structure; a virtualization window is arithmetic. Those you can genuinely run.

React — Try it Yourself
Loading editor…
Preview

Stating that boundary explicitly is not a hedge. A volume that let a browser result stand in for a device result would teach confident wrong answers, and the whole method of this Library — measure it, then say what you measured — depends on the distinction between the two being visible.

Common Mistakes

  1. Expecting "write once, run anywhere". The claim was always "learn once, write anywhere" — the model transfers, the platform code does not.

  2. Budgeting React Native as "React". It is React plus a platform: navigation, Yoga layout, permissions, store review, two build toolchains.

  3. Thinking <View> is a styled div. Measured in 0.81.4, it resolves to the string 'RCTView', which the host turns into a real UIView or Android View.

  4. Assuming React had to change for this. React Native's peer dependency is react: ^19.1.0 — the same React, with a different renderer alongside it.

  5. Treating a prop update as a mutation. It is a batched, asynchronous message, which is why you cannot read back a layout you just requested.

  6. Trying to send a function across the boundary. Only serialisable values cross; callbacks are registered and referenced by id.

  7. Reaching for a web view for a real screen. It is always faster to build and it reintroduces the exact feel you adopted React Native to avoid.

  8. Trusting undated architecture descriptions. The bridge answer and the JSI answer differ, and articles about the former outnumber the latter by years.

  9. Reading a description instead of the source. The JavaScript half ships in your node_modules; View.js and ViewNativeComponent.js take five minutes and settle the question.

  10. Letting a browser result stand for a device result. rn try is react-native-web, so Platform.OS reads "web" and layout is browser flexbox rather than Yoga.

Interview Corner

Interview Corner — the same idea at three levels. Answer yours; recognise the one above it.
Warm-up
How is React Native different from a web app in a web view?
Model answer — A web view ships a browser and runs HTML inside it, so the interface is web widgets and inherits browser behaviour — scroll physics, text selection, keyboard handling — which is why those apps feel subtly wrong on a phone. React Native renders real platform views instead: your <View> becomes an actual UIView on iOS and an Android View, driven from a JavaScript thread. So you inherit the platform's own widgets, gestures and accessibility, and you get OS behaviour changes for free. The cost is that your code and your views are in different places, so every interface update is an asynchronous batched message rather than a direct mutation.
Intermediate
React Native arrived two years after React and barely changed it. Why was that possible?
Model answer — Because React already separated deciding what the UI should be from performing the changes. The reconciler runs components and diffs the resulting tree and knows nothing about browsers; a host config supplies the primitives — create an instance of this type, append this child, update these props. react-dom is one host config where "create an instance" is document.createElement; React Native is another where it creates a native view. You can see this in the packages: React Native declares a peer dependency on the same React and ships its own renderers, ReactNativeRenderer and ReactFabric, in place of react-dom. That seam was originally drawn so rendering logic could be tested without a browser, and it turned out to be what made a second target possible — which is also how react-native-web maps the same components back onto the DOM.
Advanced
When would you argue against React Native for a new mobile product?
Model answer — When the product's value is concentrated in something the boundary makes expensive or the ecosystem does not cover. Heavy real-time graphics or camera processing means most frames are spent in native code, so the JavaScript layer adds a boundary without buying much. A product built around a platform capability that ships as a native SDK with no maintained wrapper means writing and maintaining native modules on both platforms, which is the cost you adopted React Native to avoid. And if the team is genuinely one platform only, with no plan for the second, a single native codebase is simpler than a cross-platform one. I would argue for it when the app is mostly screens, lists, forms and navigation over a network — which is most apps — and when a web team already exists, since the programming model transfers even though the platform does not. What I would not accept as a reason either way is familiarity with React alone, because that budgets for a library and the work is a platform.
Company lens — how the same topic gets asked
micro1Practical and quick: what <View> becomes on a device, why there is no div, and one concrete thing you get from native views that a web view cannot give you.
GooglePushes on the architecture: the reconciler and host-config split, what actually crosses the boundary, and how the New Architecture changes the answer.
MetaIts own framework, so expect depth and history: why "learn once, write anywhere" rather than "write once", and what the batching buys.
AmazonDecision-flavoured: when you would choose React Native against native or Flutter, what it costs the team, and how you would justify it to someone paying for two platforms.

Hands-on

Hands-on

Build the indirection this chapter is about. Implement reconcile so it walks an element tree and calls whatever host it is handed; then write two hosts — domHost mapping row and badge onto div and span, and nativeHost mapping them onto RCTView and RCTText — so that the same tree renders both ways without the components knowing. Finally implement crossings to count how many times a batched boundary is crossed. The checks print a scorecard — the stubs deliberately fail, so expect a low score before you start.

JavaScript — Try it Yourself
Loading editor…
Console
Runs automatically. Edit the code to see it change.

Summary

  • There are only three cross-platform strategies — wrap a web view, draw your own pixels, or drive real native views — and the trade-offs follow from the strategy rather than from implementation quality.
  • React Native drives the platform's own widgets, so it inherits scroll physics, accessibility and OS updates, and pays for a boundary instead.
  • The slogan was "learn once, write anywhere" — a claim about the programming model, never about the code.
  • React was never a DOM library. The reconciler decides what the UI should be; a host config performs it, and react-dom is only one host.
  • React Native uses the same React — peer dependency ^19.1.0 in 0.81.4 — shipping ReactNativeRenderer and ReactFabric in place of react-dom.
  • <View> resolves to the string 'RCTView', exactly as <div> is a string, and the host turns it into a real UIView or Android View.
  • Every interface update is a message, not a mutation: asynchronous, batched, and restricted to serialisable values.
  • Batching is what makes the boundary survivable — 60 operations became 3 crossings — and also why you cannot read back a layout you just requested.
  • Read the source when a description feels vague, and keep the version beside the claim, because this framework has already changed its answers once.
  • rn try is react-native-web, honest about structure and not about the platform, which is why Platform.OS reads "web" in this volume's blocks.

In the next chapter, The Two Threads, we take the boundary seriously — what actually runs on the JavaScript thread, what the UI thread owns, and why a dropped frame is a scheduling problem rather than a slow function. It is the chapter that explains why the same code can be smooth on a desktop browser and visibly janky on a phone.

Editorial reflection

The thing I find genuinely elegant here is that React Native was made possible by a boundary drawn for an unrelated reason. React's reconciler was separated from its DOM operations so that rendering logic could be tested without a browser — a testing convenience — and that seam is precisely what allowed a native renderer two years later, and react-native-web after that. You cannot plan for that. What you can do is notice that the boundaries which pay off unexpectedly tend to be the ones drawn along a real conceptual joint rather than along a convenience, and "decide what the UI is" against "make it so" is a real joint.

The measurement that improved this chapter was reading ViewNativeComponent.js. I could have written "View renders to a native component" — accurate, and it teaches nothing. Finding that it bottoms out at the string 'RCTView', sitting next to a real RCTView.h in the same package, turns a vague claim into a mechanism a reader can hold: it is a name, resolved by a host, exactly like 'div'. Forty lines of source did more than any amount of careful phrasing would have.

Where I want to be honest is about this volume's playground. Eighteen volumes have promised that a code block runs and what you see is true, and this one cannot keep that promise for anything platform-specific — Platform.OS reads "web" in the blocks above, and layout is browser flexbox rather than Yoga. I considered hiding that behind carefully chosen examples that never touch the seam. That would have been worse: a reader who eventually discovers it would rightly distrust everything else. So the boundary is stated in chapter one, demonstrated in a runnable block, and the chapters where it matters will hand you a simulator instead. A stated limitation is a smaller cost than a hidden one.

Q&A · 0

Enrol to ask questions and join the discussion.

No questions yet — be the first to ask.