E-NO Logo
EN FR
React Native advanced concepts 7 Min Read

React Native advanced concepts explained with practical examples: practical implementation guide

calendar_today Published: 2026-07-17
update Last Updated: 2026-07-17
analytics SEO Efficiency: 97%
Technical guide illustration for React Native advanced concepts explained with practical examples: practical implementation guide.

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:

  1. Clarify goals
  • Platforms: Android, iOS, Expo.
  • Budgets: startup, memory, FPS, and bundle size.
  • Constraints: payments, offline, low-end devices.
  1. Choose architecture
  • Enable Hermes.
  • Prefer the New Architecture when dependencies support it.
  1. Implement modules
  • Move hot or chatty native paths to TurboModules or JSI.
  1. Build UI on Fabric as needed
  • Create native views only when primitives are insufficient.
  1. Optimize data flows
  • Design REST calls with cancelation, caching, and offline.
  1. Add payments
  • Isolate Google Play Billing and Stripe in native modules.
  • Validate purchases server side.
  1. Test locally
  • Measure startup, FPS, memory, and error recovery.
  1. 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>

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 = (char)toupper((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) {
    // Load C++ library then install JSI binding.
    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 helpers:

// 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:

  1. Define a TypeScript spec for your module API.
  2. Implement native code per platform.
  3. Use codegen to produce type-safe bindings.

Example spec (device info):

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

Android 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

Cancelation 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.
  • Query product details.
  • Launch purchase flow on the UI thread.
  • Receive purchase token, acknowledge or consume.
  • Validate on your server.

Stripe flow:

  • Use native SDKs to present PaymentSheet.
  • 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 backoff.
  • Log purchase states for audit and reconciliation.

Expo and the New Architecture

  • Hermes is enabled by default in modern Expo SDKs.
  • Use prebuild when you need custom native modules (billing, Stripe).
  • Keep native versions aligned with your Expo runtime.
  • For payments, plan on prebuild or config plugins to include native SDKs.

Local Pilot Plan

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

Steps:

  1. Enable Hermes and record cold start time on a mid-tier Android device.
  2. Add a TurboModule exposing getFreeMemory() and display it in a screen.
  3. Implement a Fabric ProgressBar with value and color; mark complete when value reaches 100.
  4. Build a fetchWithCache wrapper with AbortController; cancel requests on navigation.
  5. Create a PaymentsModule interface and use mock responses in debug builds; hide real UI behind a feature flag.
  6. Add error boundaries and retry buttons for network calls.
  7. 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.
  8. 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.

Article Quality Score

Reader usefulness 97%
  • check_circle Reader-ready guide
  • check_circle Practical examples included
  • check_circle Clean SEO article URL