## Intro

This guide explains advanced React Native concepts with practical examples you can run locally. We focus on the New Architecture (Fabric, TurboModules, JSI), threading and performance, native UI, data flows, payments (Google Play Billing and Stripe), and how to integrate with Expo. You will learn how these parts work, when they matter, and how to adopt them safely in production.

## Workflow Overview

A clear, repeatable engineering workflow keeps advanced topics manageable:

- Clarify goals

- Platforms: Android, iOS, Expo

- Budgets: startup time, memory, FPS, and bundle size

- Constraints: payments, offline support, low-end devices

- Choose architecture

- Enable Hermes on all new apps

- Prefer the New Architecture when dependencies support it

- Implement modules

- Move hot or chatty native paths to TurboModules or JSI

- Build UI on Fabric as needed

- Create native views only when React primitives are insufficient

- Optimize data flows

- Design REST calls with cancellation, caching, and offline support

- Add payments

- Isolate Google Play Billing and Stripe in native modules

- Validate purchases server-side

- Test locally

- Measure startup, FPS, memory, and error recovery

- Gradually roll out

- Use feature flags and monitor device diversity

## React Native Architecture Deep Dive

Key pieces:

- Hermes : a small JavaScript engine optimized for mobile, improving startup and memory

- JSI : a C++ interface letting JS call native code without JSON serialization overhead

- TurboModules : the module system using JSI for faster method calls and lazy loading

- Fabric : the new renderer that synchronizes UI updates with a concurrent layout engine (Yoga) and improves consistency between platforms

When to adopt:

- Enable Hermes on all new apps

- Use TurboModules when JS-to-native calls are frequent or compute-heavy

- Build Fabric components for custom, high-performance UI

### Minimal JSI Function Example (Android C++)

Expose a fast native helper via JSI for a hot path (for example, a simple string operation).

// cpp/JSIStringUtil.cpp
#include <jsi/jsi.h>
#include <string>
#include <cctype>

using namespace facebook;

void installStringUtil(jsi::Runtime &rt) {
 auto toUpper = jsi::Function::createFromHostFunction(
 rt,
 jsi::PropNameID::forAscii(rt, "toUpperFast"),
 1,
 [](jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, size_t count) -> jsi::Value {
 if (count < 1 || !args[0].isString()) {
 throw jsi::JSError(rt, "toUpperFast requires a string");
 }
 auto s = args[0].asString(rt).utf8(rt);
 for (auto &c : s) c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
 return jsi::String::createFromUtf8(rt, s);
 }
 );
 rt.global().setProperty(rt, "toUpperFast", std::move(toUpper));
} 
 // android/src/main/java/com/example/MyPackage.java
public class MyPackage implements ReactNativeHost.ReactInstanceEventListener {
 @Override
 public void onReactContextInitialized(ReactContext context) {
 System.loadLibrary("jsi_stringutil");
 ReactApplicationContext rac = (ReactApplicationContext) context;
 rac.runOnNativeModulesQueueThread(() -> {
 com.example.StringUtilInstaller.install(rac);
 });
 }
} 
 // JS usage
const out = globalThis.toUpperFast("hello"); 
 Use this pattern only when a real profile shows JS is the bottleneck.

## Threading, Rendering, and Performance

Threads:

- JS thread runs application logic

- UI thread handles view updates and input

- Fabric may use a render thread to mount updates

- TurboModules can use background pools for native work

Guidelines:

- Keep work on the JS thread small; chunk long loops with setImmediate or requestAnimationFrame

- Batch state updates and avoid unnecessary re-renders (memoize expensive children)

- Offload heavy work to native via JSI or background tasks

- Avoid synchronous bridges and large JSON payloads

Rendering tips:

- Flatten view hierarchies

- Use FlatList with proper getItemLayout when possible

- Avoid creating new object/array props in render; memoize styles

- Prefer Animated or platform-native animations for complex cases

Quick helper:

// Debounced state update to reduce re-renders
function useDebounced<T>(value: T, ms = 150) {
 const [v, setV] = React.useState(value);
 React.useEffect(() => {
 const t = setTimeout(() => setV(value), ms);
 return () => clearTimeout(t);
 }, [value, ms]);
 return v;
} 

## Native Modules and UI Components

 TurboModule plan:

- Define a TypeScript spec for your module API

- Implement native code per platform

- Use codegen to produce type-safe bindings

Example spec (device info):

// DeviceSpec.ts
export interface Spec {
 getFreeMemory(): Promise<number>;
} 
 Android implementation sketch:

class DeviceModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
 override fun getName() = "DeviceModule"
 @ReactMethod
 fun getFreeMemory(promise: Promise) {
 val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
 val mi = ActivityManager.MemoryInfo()
 am.getMemoryInfo(mi)
 promise.resolve(mi.availMem.toDouble())
 }
} 
 Fabric view plan (ProgressBar):

- Props: value (0..100), color

- Native draws a simple bar and emits onComplete at 100

- Validate props and coerce out-of-range values

Safety:

- Validate inputs in native code

- Keep methods idempotent

- Emit descriptive errors for recoverable cases

## Data, Networking, and Offline

Cancellation and caching:

// fetchWithCache.ts
const memCache = new Map<string, any>();

export async function fetchWithCache(
 url: string,
 opts?: RequestInit & { cacheKey?: string; signal?: AbortSignal }
) {
 const key = opts?.cacheKey ?? url;
 const cached = memCache.get(key);
 if (cached) {
 // Serve cached first, then refresh in background
 setTimeout(() => refresh(url, key).catch(() => {}), 0);
 return cached;
 }
 return refresh(url, key, opts?.signal);
}

async function refresh(url: string, key: string, signal?: AbortSignal) {
 const res = await fetch(url, { signal });
 if (!res.ok) throw new Error(`HTTP ${res.status}`);
 const json = await res.json();
 memCache.set(key, json);
 return json;
} 
 Offline mutation queue:

import AsyncStorage from "@react-native-async-storage/async-storage\);\
import NetInfo from "@react-native-community/netinfo\);\

type Job = { id: string; url: string; body: any; method: string };\
const KEY = "offlineQueue\);\
\
export async function enqueue(job: Job) {\
 const raw = (await AsyncStorage.getItem(KEY)) || "[]\);\
 const arr: Job[] = JSON.parse(raw);\
 arr.push(job);\
 await AsyncStorage.setItem(KEY, JSON.stringify(arr));\
}\
\
export async function flush() {\
 const state = await NetInfo.fetch();\
 if (!state.isConnected) return;\
 const raw = (await AsyncStorage.getItem(KEY)) || "[]\);\
 const arr: Job[] = JSON.parse(raw);\
 const next: Job[] = [];\
 for (const j of arr) {\
 try {\
 const res = await fetch(j.url, {\
 method: j.method,\
 body: JSON.stringify(j.body),\
 headers: { "Content-Type": "application/json" }\
 });\
 if (!res.ok) throw new Error(String(res.status));\
 } catch (e) {\
 next.push(j); // keep for retry\
 }\
 }\
 await AsyncStorage.setItem(KEY, JSON.stringify(next));\
}\ 
 Server tips:

- Idempotent endpoints to tolerate retries

- Short-lived tokens; rotate regularly

## Payments Examples

Google Play Billing flow (Android):

- Initialize BillingClient once during app startup

- Query product details with queryProductDetailsAsync

- Launch purchase flow on the UI thread via launchBillingFlow

- Receive purchase token in onPurchasesUpdated ; acknowledge or consume

- Validate purchase token on your server before granting entitlement

Stripe flow:

- Use native SDKs to present PaymentSheet (iOS) or PaymentSheet via Stripe Android SDK

- Never handle raw card data in JS

- Create PaymentIntent on server; confirm with client secret in the app

Shared module API sketch:

// Payments.ts
export interface PaymentsSpec {
 isReady(): Promise<boolean>;
 listProducts(skus: string[]): Promise<{ sku: string; price: string }[]>;
 startPurchase(sku: string): Promise<{ success: boolean; token?: string; error?: string }>;
 presentPaymentSheet(clientSecret: string): Promise<{ success: boolean; error?: string }>;
} 
 Error handling:

- Distinguish user cancellations from failures

- Retry transient network errors with exponential backoff

- Log purchase states for audit and reconciliation

## Expo and the New Architecture

- Hermes is enabled by default in modern Expo SDKs (SDK 48+)

- Use expo prebuild when you need custom native modules (billing, Stripe)

- Keep native versions aligned with your Expo runtime version

- For payments, plan on prebuild or config plugins to include native SDKs

## Local Pilot Plan

Goal: prove Hermes, a TurboModule, a Fabric view, REST cancellation, and a safe payments interface.

Steps:

- Enable Hermes and record cold start time on a mid-tier Android device (e.g., Snapdragon 765G)

- Add a TurboModule exposing getFreeMemory() and display it in a screen

- Implement a Fabric ProgressBar with value and color props; mark complete when value reaches 100

- Build a fetchWithCache wrapper with AbortController ; cancel requests on navigation

- Create a PaymentsModule interface and use mock responses in debug builds; hide real UI behind a feature flag

- Add error boundaries and retry buttons for network calls

- Define success metrics: startup < 2s, smooth 60 fps on list scroll, canceled request never sets state after unmount, and payments mock flow works end-to-end

- Write a short manual test script and run on Android and iOS simulators and at least one physical Android device

Exit criteria:

- All metrics met

- No unhandled promise rejections

- Logs show clean init and teardown without leaks

## Conclusion

You learned how Hermes, JSI, TurboModules, and Fabric fit together, how to reason about threads and rendering, and how to structure data and payments safely. Start with the pilot, measure real gains, and then expand module by module. Keep changes behind flags, validate on devices across tiers, and document contracts between JS and native code so teams can evolve confidently.