React Native

React Native Development in TypeScript

One codebase in a language most teams can already hire for, rendering real platform components on both stores. We build it the way it survives three years: typed, measured on cheap hardware, and upgraded on a schedule rather than in a panic.

  • TypeScript throughout
  • Over-the-air fixes
  • Swift and Kotlin when needed
The Stack

What Is Actually in a React Native Project We Hand Over

React Native is a rendering layer, not an application framework. Everything around it is a set of choices, and the choices are what you inherit.

TypeScript, not optional

Every screen, every API response and every navigation route is typed. On a dynamically typed codebase the difference is not academic: it is the class of bug that otherwise reaches a user as a blank screen because a field the server stopped sending was read anyway.

Navigation as a real structure

Stacks, tabs and modals set up so the hardware back button on Android, deep links, notification taps and state restoration all end up in the right place. Navigation is where hastily built React Native apps fall apart first.

Server state kept separate

Data fetched from your API is cached, revalidated and retried by a dedicated layer, rather than being copied into a global store where it goes stale. Local interface state stays small and local. This one decision removes most of the state management arguments.

A design system, not loose styles

Spacing, type, colour and components defined once so the hundredth screen looks like the first. Also the only practical way to support dark mode and larger system font sizes without revisiting every file.

Secrets where they belong

A JavaScript bundle is readable by anyone who wants to read it, so tokens go in the platform keychain or keystore and anything genuinely sensitive stays on your server. Treating the bundle as private is one of the more common mistakes we inherit.

A build pipeline from week one

Automated builds, signed artefacts and distribution to testers configured at the start, not the week before launch. Store credentials and signing live under your accounts throughout.

The New Architecture

What Changed Underneath, in Language That Affects a Budget

You do not need the internals. You do need to know why it matters, because it changes what a React Native app can be asked to do.

How it used to work

  • JavaScript and native code were two separate worlds talking through a queue, passing messages as serialised text
  • Every message was asynchronous, so nothing native could answer a question immediately
  • Anything that needed a quick answer — measure this view, then position that one — showed up as a flicker or a one-frame jump
  • All native modules were created at startup whether the app used them or not
  • A gesture tracking a finger had to hop the queue on every frame, which is why animation had a reputation

What it means for your app now

  • Native modules load when they are first used, which measurably improves cold start
  • Native code can be called directly when an immediate answer is required, so layout stops flickering
  • The interface between the two sides is generated from typed definitions, turning a whole class of runtime crash into a build error
  • Layout is calculated on its own thread, so a heavy screen is less likely to block the one the user is touching
  • Practically: the ceiling for what is comfortable to build in React Native moved up, and the gap to native narrowed at exactly the places it used to be widest

New projects get this by default and it is not a decision you have to make. For an existing app it is a migration, and the cost is driven almost entirely by how many unmaintained native dependencies the project is carrying. That audit is the first thing we do on an app rebuild.

Project Shape

Managed Tooling or a Bare Project

The old version of this argument was about whether you could write native code. You can, in both. The real question is who owns the build system.

Our default

The Managed Toolchain

  • A build service, so a signed build for either store is one command
  • Over-the-air updates as a first-class, supported feature rather than a bolt-on
  • A curated set of libraries for camera, notifications, storage, location and the rest, tested together and upgraded together
  • Native code still possible, through configuration plugins that generate the platform projects
  • Framework upgrades arrive as a coordinated set, which is the single biggest reduction in long-term pain

When it earns its keep

A Bare Project

  • A vendor SDK that fights the managed build system and will not be reasoned with
  • Adding React Native screens into an existing native app rather than starting fresh
  • Your own engineers will maintain the native side and want the platform projects in the repository
  • Heavy native work at the core of the product, where the platform projects are edited constantly
  • The trade is control now in exchange for owning every upgrade yourself later

We decide this before the first sprint. Moving between the two later is not technically hard, but it disrupts a schedule at exactly the point where there is no slack in it.

Over-the-Air Updates

Fixing Something on a Friday Without Asking Anyone’s Permission

The JavaScript bundle is loaded at runtime, so it can be replaced without a store submission. This is React Native’s most useful operational advantage and the one most easily abused.

What you can ship this way

Bug fixes in JavaScript, copy and translation corrections, layout and styling changes, configuration, feature flags, and anything else that lives entirely in the bundle. It reaches installed apps on their next launch.

What still needs a submission

Anything touching native code, a new permission or capability, a new native dependency, store metadata, screenshots, or a change to what the app fundamentally is. The native half of the app did not move, so the stores have not seen it.

Where people get into trouble

Both stores expect these updates to fix and refine rather than to introduce functionality review never saw. Using the channel to slip a whole new feature past review is a policy problem, and it is the kind that surfaces later, at the worst time.

We treat a bundle push as a real release

An over-the-air update reaches users faster than a store release, which means a bad one reaches them faster too. So it goes out to a percentage first, with crash reporting watched before it widens, and with a route back to the previous bundle that does not depend on anyone being at a desk.

Updates are also tied to a specific native version. An app on an older build must not receive a bundle written against newer native modules, which is the failure that turns a small fix into a crash loop on a subset of devices.

What it is genuinely good for

  • A crash found an hour after launch, fixed the same afternoon
  • Wording that turned out to confuse people, corrected without a week of waiting
  • A feature flag flipped off when a partner API starts misbehaving
  • Testers on the current bundle without a new build being distributed
Performance

The Four Places a React Native App Gets Slow

Not mysterious, and not inherent to the framework. Each one has a known cause and a known fix, and each one is invisible on a fast phone.

Long lists

  • Rows re-rendering because a new function or object is created on every parent render
  • Images fetched at full resolution and scaled down by the layout instead of being resized before they arrive
  • Work inside the row — date formatting, filtering, sorting — that should have happened once when the data loaded
  • A list that has to measure every row because nobody told it the heights
  • When rows are genuinely heavy, with video, maps or charts, a recycling list with native backing rather than the default

Animation and gestures

  • Anything following a finger must run on the interface thread, driven natively, not recalculated in JavaScript each frame
  • Gesture handling belongs in the native gesture system so it interacts correctly with scroll views and the back swipe
  • Transitions between screens are where a demo built on a flagship stops being representative
  • Tested on the slowest device in the matrix, because that is where sixty frames a second is actually in question

Startup

  • Under the New Architecture, native modules load on first use rather than all at launch, which helps considerably
  • What still hurts is loading too much JavaScript before the first screen, and doing network work before anything is drawn
  • A cold start target is agreed at scoping and checked on a low-end Android device, not on a laptop simulator
  • A splash screen that hides a slow start is a cosmetic fix, and users learn to read it correctly

App size

  • The runtime adds a floor of a few megabytes before your code exists, which is smaller than the alternatives but not nothing
  • Dependencies are the real driver. One convenience library can cost more than the feature it provides is worth.
  • Android app bundles let the store deliver only what each device needs, which recovers a meaningful share of it
  • We review the dependency list before committing, because removing a library after launch is harder than never adding it
Native Modules

When the Shared Code Has to Stop and Native Code Has to Start

Most apps need this somewhere. Knowing where, before you start, is what keeps a React Native project from turning into two native projects with an extra layer.

Someone has already built it

Camera, notifications, biometrics, secure storage, maps, in-app purchase, file handling: for the common cases a maintained package already exists and wraps both platforms. The work is choosing carefully and checking the project is alive, not writing it.

Or we write it

A small typed interface on the JavaScript side, a Swift implementation, a Kotlin implementation. We have people who write both, which is not universally true of agencies selling React Native, and it is the question worth asking any of them.

Where the boundary bites

Data crossing between the two sides has a cost, so a design that streams camera frames or sensor readings across it is fighting the architecture. That work belongs on the native side, sending results rather than raw data.

If the honest answer is that a fifth of the app or more will be native, the recommendation changes. That conversation is on cross-platform app development, along with the case for Flutter instead.

Sharing With Web

If You Already Have a React Application

This is the strongest argument for React Native over the alternatives, and it is also routinely overstated. Here is the line between the two.

Shares well

  • TypeScript types, and an API client generated from the same schema
  • Validation rules, so a form cannot accept on mobile what the website rejects
  • Pricing, entitlement, permission and other business logic that must not diverge
  • Formatting, dates, currency and localisation strings
  • Analytics event definitions, so the two products report the same funnel
  • The habits of your existing team, which is worth more than any of the above

Does not share, and should not

  • Components. Layout on a phone is a different problem, not a narrower one.
  • Navigation. A back button, a tab bar and a browser history are three different models.
  • Anything involving the keyboard, gestures, permissions or offline state
  • The information architecture. A phone screen holds less, and pretending otherwise is how apps become unusable.
  • A web app wrapped in a shell. Both stores have views about that, and so do users.

The realistic outcome is a shared core package plus a mobile app that looks nothing like your website in code. That is still a genuine saving, and it removes the bug where two products disagree about a rule.

Upgrades

The Maintenance Nobody Quotes For

React Native apps do not rot because the framework changes. They rot because the platforms beneath it do, and the framework has to follow.

01

The platforms move

Both stores require apps to be built against recent SDKs, and every annual OS release changes something about permissions, background work or layout.

02

The framework follows

A new framework version absorbs those changes. Staying on an old one eventually means you cannot build against the SDK the store now requires.

03

The libraries follow it

Every native dependency has to be updated too, and the weakest link is whichever package has quietly stopped being maintained.

04

Which is why we schedule it

One or two planned upgrades a year, each a known piece of work. The alternative is one enormous unplanned one, in the week you discover you cannot ship.

The cost of an upgrade scales with how far behind you are, so waiting is the expensive option
Every extra native dependency is another thing that has to be alive on upgrade day
Managed tooling upgrades a tested set together, which is most of why we default to it
Upgrade work is included in our support plans rather than invoiced as a surprise
FAQ

React Native Questions We Get Asked

Should we use Expo or a bare React Native project?

Start with the managed tooling unless there is a concrete reason not to. It gives you a build service, over-the-air updates, a coherent set of maintained libraries for the things every app needs, and a way to run the app on a tester device without anyone installing Xcode. The historic objection was that you could not add native code, and that is no longer true: you can write a config plugin, generate the native projects when you need them, and keep everything else. The case for a bare project is narrower than it used to be, and it is usually one of these: a native SDK that fights the build system, an existing native app you are adding React Native into, or a client whose own engineers will maintain the native side. We make the call before the first sprint, because moving between the two mid-project is disruptive rather than difficult.

Can we ship JavaScript updates without going through the stores?

Yes, within limits that are worth understanding before you build a process around it. Because the JavaScript bundle is loaded at runtime, a fix to that bundle can be delivered straight to installed apps and applied on the next launch. Anything touching native code, permissions, capabilities or store metadata still needs a normal submission. The policy line on both stores is that these updates must not change what the app fundamentally is or do things review never saw; bug fixes, copy corrections, layout adjustments and configuration changes are the intended use. Shipping a whole new feature this way to avoid review is a policy problem rather than a clever trick. We also treat an over-the-air update as a real release: staged to a percentage first, monitored, and reversible, because a bad bundle pushed to everyone at once is worse than a bad build.

We already have a React web app. How much of it can we reuse?

The logic, not the interface. What shares cleanly is everything below the screen: TypeScript types and the API client generated from them, validation rules, formatting and currency handling, permission and entitlement logic, state management patterns, and the vocabulary your team already uses for all of it. That is a genuine saving and it also removes a class of bug where web and mobile quietly disagree about a business rule. What does not share is the components. Layout, navigation, gestures, keyboard handling and platform behaviour are different problems on a phone, and attempting to run web components on mobile produces something that works in a demo and feels wrong in a hand. The realistic expectation is a shared core package plus a mobile app that looks nothing like the website in code, which is also true of apps built any other way.

Why is our long list slow, and can it be fixed?

Almost always, and it is usually four causes rather than one. Rows that re-render because a new function or object is created on every parent render. Images loaded at full resolution and scaled down by the layout instead of being resized before they arrive. Row components doing work, such as date formatting or filtering, that should have happened once when the data loaded. And a list that measures every row because the heights are unknown, rather than being told them. Fixing those normally gets an ordinary feed to scroll smoothly even on a cheap Android device. When the rows themselves are genuinely heavy, with video, maps or charts, a recycling list implementation with native backing is the next step. We measure this on the slowest device in the agreed matrix, because the problem does not exist on a modern iPhone and shipping without checking is how it reaches your reviews.

What does the New Architecture change for us?

Commercially, three things. Startup improves, because native modules are created when they are first used rather than all of them at launch. Interactions that need an immediate answer from the native side, such as measuring a view before drawing, stop having to wait for a round trip across an asynchronous queue, which removes a category of visible flicker and one-frame jump. And the interfaces between JavaScript and native are generated from typed definitions, so a mismatch becomes a build error instead of a crash on a user device. Architecturally it is a rewrite of how the two halves talk to each other. New projects get it as the default and it is not something you decide. For an existing app the work is proportional to how many unmaintained native dependencies it carries, which is exactly the audit we would run before quoting an upgrade.

How often will the app need a React Native upgrade?

Plan on a meaningful upgrade roughly once or twice a year, and treat it as scheduled maintenance rather than an emergency. The pressure does not come from React Native wanting to be new; it comes from the platforms underneath. Apple and Google require apps to be built against recent SDKs, each annual OS release breaks something, and the library ecosystem follows the framework rather than your release plan. The cost of an upgrade scales with how far behind you are and how many native dependencies you depend on, which is why sitting still for two years is the expensive option rather than the safe one. Apps on the managed tooling have an easier time of it, because the upgrade is coordinated across a set of libraries that are tested together.

Get a React Native Build Scoped Honestly

Send over what the app has to do. You get a written scope, a view on Expo versus bare, the native modules it will need and a cost range within two business days.