React Native: How JavaScript Renders Native UIs

Series: Cross-Platform Mobile Development
Previous: Ionic & Capacitor: How Web Tech Becomes Mobile Apps
Next: Flutter: How Dart Paints Every Pixel
React Native takes a fundamentally different approach from Ionic. Instead of running your app in a WebView, React Native uses JavaScript to control actual native UI components. When you write <View>, it becomes a real UIView on iOS and android.view.View on Android.
No WebView. No HTML. No CSS. Real native components, orchestrated by JavaScript.
This sounds elegant — and it is. But the mechanism that makes this possible has gone through a massive architectural overhaul. Understanding both the old and new architecture reveals why React Native struggled with performance for years and how it finally solved those problems.
The Core Idea
In a web app, the browser renders everything. In React Native, the native platform renders everything:
You write React components, but instead of web elements, you use React Native primitives:
| React (Web) | React Native | iOS Native | Android Native |
|---|---|---|---|
<div> | <View> | UIView | android.view.View |
<span> | <Text> | UILabel | TextView |
<img> | <Image> | UIImageView | ImageView |
<input> | <TextInput> | UITextField | EditText |
<button> | <Pressable> | UIButton | Button |
<ul> | <FlatList> | UITableView | RecyclerView |
// This looks like React, but renders native components
import { View, Text, Image, Pressable } from 'react-native';
function ProfileCard({ user }) {
return (
<View style={styles.card}>
<Image source={{ uri: user.avatar }} style={styles.avatar} />
<Text style={styles.name}>{user.name}</Text>
<Pressable onPress={() => follow(user.id)}>
<Text>Follow</Text>
</Pressable>
</View>
);
}This code creates real UIView, UIImageView, UILabel, and UIButton instances on iOS — not web elements inside a WebView.
The Old Architecture (Pre-2022)
Understanding the old architecture explains why React Native had performance issues and why the rewrite was necessary.
Three Threads
React Native ran on three separate threads:
The Bridge Problem
The bridge was the critical bottleneck. All communication between JavaScript and native code went through this bridge:
- JavaScript decides to create a
<View>with certain styles - The instruction is serialized to JSON and sent across the bridge
- The shadow thread receives it, calculates layout with Yoga (a Flexbox engine)
- Layout results are serialized to JSON and sent back across the bridge
- The UI thread receives the final instructions and creates native views
Every message crossed the bridge as serialized JSON. For simple UIs, this was fine. But for complex interactions:
The symptoms:
- Scroll jank — touch events had to cross the bridge to JS and back, adding latency
- Animation stuttering — JS-driven animations sent hundreds of messages per second across the bridge
- Slow startup — the entire React Native runtime had to initialize before any UI appeared
- Dropped frames — if the bridge was congested, frames were skipped
This is why React Native got a reputation for "almost native but not quite" performance.
The New Architecture (2022-Present)
React Native's New Architecture is a ground-up rewrite of the communication layer. It eliminates the bridge entirely.
Three Pillars
1. JSI (JavaScript Interface)
The biggest change. JSI replaces the bridge with direct C++ bindings between JavaScript and native code.
Instead of serializing everything to JSON and sending it over an asynchronous bridge, JavaScript can now call C++ functions directly — and C++ can call back into JavaScript directly.
Old Architecture:
JS → serialize to JSON → bridge → deserialize → Native
Native → serialize to JSON → bridge → deserialize → JS
New Architecture (JSI):
JS ↔ C++ ↔ Native (direct function calls, no serialization)This means:
- No serialization overhead — objects are shared by reference, not copied
- Synchronous calls when needed — JS can call native code and get a result immediately
- Any JS engine — JSI is engine-agnostic (works with Hermes, V8, or JavaScriptCore)
2. Fabric (New Renderer)
Fabric is the new rendering system that replaces the old UI manager. Key improvements:
Concurrent rendering: Fabric integrates with React 18's concurrent features. UI updates can be prioritized — a user tap gets higher priority than a background data refresh.
Synchronous layout: In the old architecture, layout was calculated on a separate thread with async bridge communication. Fabric can compute layout synchronously when needed, eliminating the "layout flash" (where views appeared briefly in wrong positions).
Shared ownership: Both JavaScript and native code can reference the same UI node. In the old architecture, only one side "owned" a view at a time.
3. TurboModules
TurboModules replace the old Native Modules system. Two key improvements:
Lazy loading: Old Native Modules were all initialized at startup, even if never used. TurboModules load only when first accessed — significantly improving startup time.
Type-safe interface: TurboModules use CodeGen to generate C++ interface definitions from TypeScript specs. This means type errors are caught at build time, not at runtime.
// TurboModule spec (TypeScript)
import { TurboModule, TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
getConstants(): { apiUrl: string };
fetchUser(id: number): Promise<{ name: string; email: string }>;
}
export default TurboModuleRegistry.getEnforcing<Spec>('UserModule');// Android implementation
public class UserModule extends NativeUserModuleSpec {
@Override
public Map<String, Object> getConstants() {
return Map.of("apiUrl", "https://api.example.com");
}
@Override
public void fetchUser(double id, Promise promise) {
// Native implementation
promise.resolve(/* user data */);
}
}The CodeGen generates a C++ interface that both sides must conform to — catching mismatches at compile time instead of crashing at runtime.
Hermes: React Native's JavaScript Engine
React Native ships with Hermes, a JavaScript engine built specifically for mobile:
Why Not Just Use V8 or JavaScriptCore?
V8 (Chrome's engine) and JavaScriptCore (Safari's engine) are designed for browsers. They optimize for long-running sessions with JIT compilation. Mobile apps have different needs:
- Fast startup — users expect apps to open instantly
- Low memory usage — mobile devices have limited RAM
- Predictable performance — no JIT warmup causing early jank
How Hermes Works
Hermes takes a different approach: Ahead-of-Time (AOT) compilation to bytecode.
Instead of shipping raw JavaScript and compiling it on the device (like V8/JSC), Hermes compiles your code to bytecode at build time. On the device, Hermes just executes pre-compiled bytecode.
Results:
- ~50% faster startup compared to JavaScriptCore
- ~30% less memory usage
- Smaller app size — bytecode is more compact than raw JavaScript
- No JIT warmup — consistent performance from the first frame
Styling: Not CSS, But Close
React Native doesn't use CSS. Instead, it uses a JavaScript object syntax that resembles CSS:
import { StyleSheet, View, Text } from 'react-native';
function Card() {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
padding: 16,
backgroundColor: '#ffffff',
borderRadius: 8,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
elevation: 3, // Android shadow
},
title: {
fontSize: 18,
fontWeight: 'bold',
color: '#1a1a1a',
},
});Layout uses Flexbox (via the Yoga engine), but with some differences from web CSS:
flexDirectiondefaults to'column'(not'row'like web)- All dimensions are unitless (no
px,em,rem) - No cascading — styles don't inherit (except text within
<Text>) - No media queries (use
DimensionsAPI oruseWindowDimensions)
Navigation: React Navigation
React Native doesn't include a built-in navigation system. React Navigation is the de facto standard:
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Profile" component={ProfileScreen} />
<Stack.Screen name="Settings" component={SettingsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}With @react-navigation/native-stack, navigation animations and gestures are handled by the native navigation controller (UINavigationController on iOS, Fragment on Android) — not JavaScript-driven animations. This gives truly native-feeling navigation.
When React Native Is the Right Choice
Perfect Fit ✅
- Your team already knows React — the learning curve is minimal
- You need native look-and-feel — native components means native behavior
- You want to share logic with a React web app — business logic, state management, API layers can be shared
- Complex apps with platform-specific UX — React Native makes it easy to customize per platform
- Apps with real-time interactions — chat, social feeds, collaborative editing
Think Twice ⚠️
- Your team doesn't know React — the React paradigm (hooks, state, effects) has a learning curve
- You need pixel-perfect custom UI — React Native components look native, which means they look different on iOS vs Android
- Heavy WebView usage — if your app is mostly web content, Ionic/Capacitor is simpler
Not Recommended ❌
- Games or graphics-intensive apps — use Unity, Godot, or Flutter
- Apps with minimal native API needs — if you just need a camera and push notifications, Ionic is simpler
- Your existing app is in Angular/Vue — React Native requires React knowledge
The Ecosystem in 2026
React Native's ecosystem is massive and battle-tested:
- Expo — the most popular way to build React Native apps. Managed workflow, EAS Build, OTA updates, and a rich SDK. Many teams never need to eject.
- React Navigation — native navigation with stack, tab, and drawer navigators
- Reanimated 3 — declarative animations that run on the UI thread (no bridge crossing)
- React Native MMKV — ultra-fast key-value storage
- React Native Skia — Skia rendering engine in React Native (for custom drawing)
- Expo Router — file-based routing (like Next.js for mobile)
Who Uses React Native?
Major apps running React Native in production:
- Meta — Facebook, Instagram, Messenger, Ads Manager
- Microsoft — Xbox app, Office, Teams, Outlook
- Shopify — entire mobile app
- Discord — iOS and Android
- Bloomberg — financial terminal mobile app
- Coinbase — cryptocurrency trading
- Flipkart — India's largest e-commerce app
These aren't toy apps. They're billion-dollar products serving hundreds of millions of users.
Key Takeaways
- React Native renders real native components, not web elements —
<View>becomesUIView/android.view.View - The old bridge architecture (JSON serialization) caused performance bottlenecks — scroll jank, animation stutter, slow startup
- The New Architecture (JSI + Fabric + TurboModules) eliminates the bridge with direct C++ bindings — a transformative improvement
- Hermes engine provides fast startup and low memory usage through ahead-of-time bytecode compilation
- The ecosystem is mature — Expo, React Navigation, Reanimated make complex apps achievable
- If you know React, you're 70% of the way to building React Native apps — the paradigm transfers directly
React Native's New Architecture was a bet that the "JavaScript controlling native components" model could work at scale. The results — shipped by Meta, Microsoft, Shopify, and Discord — prove that bet paid off.
Series: Cross-Platform Mobile Development
Previous: Ionic & Capacitor: How Web Tech Becomes Mobile Apps
Next: Flutter: How Dart Paints Every Pixel
📬 Subscribe to Newsletter
Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.
We respect your privacy. Unsubscribe at any time.
💬 Comments
Sign in to leave a comment
We'll never post without your permission.