E-NO
React Native troubleshooting 11 Min Read

React Native Troubleshooting Runbook: Version-Aware Incident Resolution

calendar_today Published: 2026-08-09
update Last Updated: 2026-08-12
analytics SEO Efficiency: 100%
Technical guide illustration for React Native Troubleshooting Runbook: Version-Aware Incident Resolution.

React Native combines JavaScript and native runtimes, which makes troubleshooting powerful but sometimes tricky. The fastest path out of most failures is a consistent, repeatable six-step workflow: inventory your environment and versions, reproduce with a narrow scope, capture the right logs, apply the smallest safe change, verify, and roll back cleanly if needed. This guide provides practical, copy-paste commands and configuration pointers for bare and Expo workflows on Android and iOS. You will learn how to read the right logs, validate network behavior, and fix common issues with module resolution, native builds, REST calls, Google Play Billing, Stripe, and RevenueCat. Each example includes expected results, failure modes, and recovery steps you can trust in day-to-day operations.

TL;DR — 5 Commands That Resolve 80% of Incidents

  • npx react-native info — capture baseline inventory
  • npx react-native start --reset-cache — clear Metro
  • cd android && ./gradlew clean assembleDebug --stacktrace — clean Android build
  • cd ios && pod deintegrate && pod install && cd .. — reset iOS pods
  • rm -rf node_modules && npm ci — universal dependency refresh

Prerequisites & Assumptions

This runbook covers React Native 0.72–0.76 across three workflow flavors: bare workflow, Expo managed workflow, and Expo development client. Host operating systems: macOS (required for iOS), Linux, and Windows. Required toolchain baselines: Node 18 or 20 LTS; single package manager (npm 9+, Yarn 1.x, or pnpm 8+); JDK 17 or 21 aligned with Android Gradle Plugin (AGP) 8.x; Android SDK with platform-tools and build-tools 34+; Xcode 15+ with iOS 15.1+ deployment target; CocoaPods 1.13+; Ruby 3.2+ (via rbenv/rvm/chruby, not system Ruby); Watchman 2024+; fastlane latest if used for CI/CD. Not covered: React Native <0.70, classic architecture-only codebases without Hermes, legacy react-native-cli global installs, or Windows native builds (React Native for Windows is out of scope).

Architecture & Log Flow Diagram (text)

[Metro Bundler] --(JS bundle)--> [Hermes/JSC Runtime] --(JSI)--> [Native Bridge]
- +-- Android: logcat (ReactNative, ReactNativeJS, System.err)
- +-- iOS: OSLog / Xcode Console (subsystem: com.apple.reactnative)
- +-- Hermes bytecode crashes -> /data/data/<pkg>/files/.hermes/*.log
- +-- Metro logs (stdout/stderr, HMR events, resolution graph)

New Architecture (Fabric/TurboModules):
[Codegen] -> [C++/ObjC/Swift/Kotlin generated] -> [Fabric Renderer / TurboModule Registry]
Log paths: Metro (Codegen output), Gradle (generateCodegenArtifactsFromSchema), Xcode (Fabric mount logs), logcat (TurboModule registration)

Environment Inventory (Expanded)

Run the following in the project root to populate an incident header. Copy the template below into your ticket.

# Incident header template — copy output into ticket
echo "=== INCIDENT HEADER ==="
echo "Date: $(date -u +"%Y-%m-%dT%H:%M:%SZ")"
echo "Git commit: $(git rev-parse HEAD 2>/dev/null || echo 'N/A')"
echo "Branch: $(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'N/A')"
npx react-native info
echo "--- Expo doctor ---"
npx expo doctor 2>/dev/null || echo "Expo CLI not available"
echo "--- Community CLI doctor ---"
npx @react-native-community/cli doctor 2>/dev/null || echo "Community CLI not available"
echo "--- Gradle ---"
gradle -v 2>/dev/null || (cd android && ./gradlew -v 2>/dev/null) || echo "Gradle not found"
echo "--- Xcode SDKs ---"
xcodebuild -showsdks 2>/dev/null | head -20
echo "--- Ruby ---"
ruby -v && which ruby
echo "--- Watchman ---"
watchman --version 2>/dev/null || echo "Watchman not installed"
echo "--- Fastlane ---"
fastlane --version 2>/dev/null || echo "Fastlane not installed"
echo "--- Node/PM ---"
node -v && (npm -v || yarn -v || pnpm -v)
echo "=== END HEADER ==="

Key inventory items to record: React Native version and template type; JS engine (Hermes default, JSC opt-in); Node and package manager versions with lockfile type; JDK distribution and version, AGP version from android/build.gradle or gradle/libs.versions.toml; Android SDK compileSdk and targetSdk; Xcode version, CocoaPods version, Ruby manager; iOS deployment target from Podfile; Watchman version; fastlane version if applicable.

Safe Configuration Baseline (Expanded)

Adopt these defaults before incident work to reduce blast radius.

Package management: Use exactly one package manager. Commit package-lock.json (npm), yarn.lock (Yarn 1.x), or pnpm-lock.yaml (pnpm). Avoid floating versions (^, ~) in package.json during incidents; pin exact versions.

Metro resolution (metro.config.js):

module.exports = {
  resolver: {
    unstable_enablePackageExports: true,
    sourceExts: ['js', 'jsx', 'json', 'ts', 'tsx', 'mjs'],
    // Add custom resolution for monorepos if needed
  },
};

React Native config (react-native.config.js):

module.exports = {
  assets: ['./assets/fonts'],
  project: {
    ios: {},
    android: {},
  },
};

Gradle properties (android/gradle.properties):

org.gradle.jvmargs=-Xmx4g -XX:MaxMetaspaceSize=512m
org.gradle.parallel=true
org.gradle.caching=true
android.enableJetifier=true
# Hermes default
hermes_enabled=true

Gradle wrapper (android/gradle/wrapper/gradle-wrapper.properties):

distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip

Podfile (ios/Podfile):

platform :ios, '15.1'
use_frameworks! :linkage => :static # or :dynamic for Swift pods
$RNFirebaseAsStaticFramework = true # if using Firebase

Secrets and config: Never embed secrets (Stripe secret keys, API tokens) in the client. Use react-native-config (bare) or expo-constants with extra (Expo) for environment-specific base URLs and publishable keys. Keep debug-only flags (cleartext, ATS exceptions) out of release builds via build variants/xcconfigs.

Core Reset Commands (Platform Matrix)

WorkflowAndroidiOSNotes
Barecd android && ./gradlew clean assembleDebug --stacktracecd ios && pod deintegrate && pod install --repo-update && cd ..Run adb reverse tcp:8081 tcp:8081 for device debugging
Expo Managednpx expo prebuild --clean --platform android then cd android && ./gradlew clean assembleDebugnpx expo prebuild --clean --platform ios then cd ios && pod install --repo-updatenpx expo start -c clears Metro cache
Expo Dev Clienteas build --profile development --platform android --localeas build --profile development --platform ios --localDevelopment client logs via adb logcat / Xcode Console
Allnpx react-native start --reset-cachenpx react-native start --reset-cacheMetro cache reset universal
Simulator resetadb emu kill + cold bootxcrun simctl erase allNuclear option for stale state

Windows PowerShell equivalents:

Set-Content -Path android\local.properties -Value "sdk.dir=$env:ANDROID_SDK_ROOT"
.\android\gradlew.bat clean assembleDebug --stacktrace
npx expo prebuild --clean --platform android

Log Sources & Correlation

TargetSourceCommand / Access
Metro bundlerTerminal stdout/stderrnpx react-native start --reset-cache
Android device logslogcat (filtered)adb logcat -s ReactNative:V ReactNativeJS:V System.err:V -v threadtime
Android buildGradlecd android && ./gradlew assembleDebug --stacktrace --scan
iOS runtimeXcode Console / OSLogxcrun simctl spawn booted log stream --predicate 'process == "MyApp"' --style compact
Hermes crash dumpsDevice file systemadb shell run-as com.myapp cat files/.hermes/*.log | npx metro-symbolicate
Expo managedExpo CLI / Metronpx expo start -c
EAS BuildEAS dashboard / CLIeas build:list --platform=android --limit=5 then eas build:view
expo-updatesDevice logsadb logcat -s ExpoUpdates:V / Xcode Console filter ExpoUpdates
New Architecture CodegenGradle / Xcode./gradlew generateCodegenArtifactsFromSchema --stacktrace / Xcode build log Codegen

Hermes crash analysis: After a native crash on Android, pull the Hermes log and symbolicate:

adb shell run-as com.myapp cat files/.hermes/*.log | npx metro-symbolicate > hermes-crash.txt
# For profiling, enable Hermes profiler:
adb shell setprop debug.hermes.profiler 1
# Then open chrome://tracing and load the generated profile

Troubleshooting Patterns (New)

Codegen / TypeScript Config Drift

Symptom: error: cannot find module 'NativeMyModule' or Codegen fails with TypeScript errors. Cause: react-native-codegen expects strict TypeScript config; js/ts mismatches in spec files. Fix:

# Verify Codegen can parse your specs
npx react-native-codegen --help
# In bare workflow, regenerate
cd android && ./gradlew generateCodegenArtifactsFromSchema --stacktrace
cd ../ios && pod install --repo-update

Expected: Generated C++/Swift/Kotlin files under build/generated/codegen (Android) and Pods/Headers/Private/React-Core/ReactCommon (iOS). Failure: TypeScript strict: false or missing skipLibCheck in tsconfig.json. Recovery: Align tsconfig.json with RN template defaults; ensure react-native-codegen version matches RN version.

TurboModule "Native module cannot be null"

Symptom: JS error Native module cannot be null for a custom TurboModule. Cause: Module not registered in ReactNativeHost (Android) or RCTBridge (iOS), or Package not added. Fix: Verify MainApplication.java / MainApplication.kt includes new MyTurboModulePackage() in getPackages(). On iOS, ensure RCTAppSetupDefaultRootView includes the module or autolinking picked it up (pod install --repo-update). Verification: adb logcat -s ReactNative:V shows TurboModuleRegistry.getEnforcing success.

Fabric "ShadowNode not found"

Symptom: Crash in FabricUIManager with ShadowNode not found for tag. Cause: Version mismatch between react-native and react-native-reanimated / react-native-gesture-handler Fabric components. Fix: Pin react-native-reanimated to version compatible with RN (e.g., RN 0.74 → Reanimated 3.10.x). Ensure newArchEnabled=true in gradle.properties and Podfile use_frameworks! :linkage => :static consistent. Recovery: Temporarily disable Fabric for the component: UIManager.setViewManagerConfigurationDescription('MyView', null) in JS.

Reanimated Worklet Errors

Symptom: WorkletError: "worklet" is not defined or ReanimatedError: [Reanimated] Mismatch. Cause: Import order (react-native-gesture-handler must be first), missing babel-plugin-reanimated, or Hermes/JSC mismatch. Fix:

// index.js or App.js — very first import
import 'react-native-gesture-handler';

Verify babel.config.js:

plugins: [
  ['react-native-reanimated/plugin', { /* options */ }],
],

Expected: No worklet errors; animations run on UI thread. Failure: Forgetting to clear Metro cache after babel config change.

Network & SSL Deep Dive

Device Debugging with adb reverse

# Metro bundler
adb reverse tcp:8081 tcp:8081
# Local API server
adb reverse tcp:3000 tcp:3000
# Verify
adb reverse --list

Charles / Proxyman SSL Pinning Bypass

  • Install CA on device/emulator: adb push charles-ssl-proxying-certificate.pem /sdcard/Download/ then Settings → Security → Install from SD card.
  • Or use network_security_config.xml (debug only):
<!-- android/app/src/debug/res/xml/network_security_config.xml -->
<network-security-config>
  <base-config cleartextTrafficPermitted="true">
    <trust-anchors>
      <certificates src="system" />
      <certificates src="user" />
    </trust-anchors>
  </base-config>
  <debug-overrides>
    <trust-anchors>
      <certificates src="system" />
      <certificates src="user" />
    </trust-anchors>
  </debug-overrides>
</network-security-config>
  • Reference in AndroidManifest.xml (debug variant only via android:networkSecurityConfig="@xml/network_security_config").

iOS ATS Exceptions (Debug Only)

Use xcconfig per configuration:

// Debug.xcconfig
NSAppTransportSecurity = {
  NSExceptionDomains = {
    "localhost" = {
      NSExceptionAllowsInsecureHTTPLoads = YES;
      NSIncludesSubdomains = YES;
    };
  };
};

Release.xcconfig: No exceptions; enforce HTTPS.

Certificate Pinning Bypass (Testing Only)

Libraries: react-native-ssl-pinning (Android), TrustKit (iOS). Never ship pinning bypass in production. Use only for QA against staging with known certs.

Fetch vs Axios vs Ky Timeout Defaults

Recommendation: Wrap all network calls with a 10–15s timeout and retry logic (exponential backoff, max 3 retries).

  • fetch: No timeout by default; implement AbortController with setTimeout.
  • axios: Default 0 (no timeout); set timeout: 10000 in instance config.
  • ky: Default 10s; configurable via timeout option.

Billing & Payments (Expanded)

Google Play Billing (react-native-iap / react-native-purchases)

ITEM_UNAVAILABLE / SKU not found:

  • Verify product ID exact match in Play Console (case-sensitive).
  • Product must be Active, not "Inactive" or "Draft".
  • Tester account must be in License Testers list (not just internal test track).
  • Use internal test track for fastest propagation (minutes vs hours).

BillingClient not ready:

import { initConnection, getProducts, purchaseProduct } from 'react-native-iap';

await initConnection();
// Wait for ready
const products = await getProducts({ skus: ['premium_monthly'] });

Verification: adb logcat -s BillingClient:V IabHelper:V shows onBillingSetupFinished with OK.

RevenueCat / react-native-purchases

Common errors:

  • Purchases.configure called multiple times or before AppRegistry.
  • Entitlement computation mismatch: customerInfo.entitlements.active['pro'] vs all['pro'].
  • logIn / logOut not balanced; anonymous IDs leak across users.
  • restorePurchases fails on simulator (requires device).

Configuration pattern:

import Purchases from 'react-native-purchases';

Purchases.configure({
  apiKey: Platform.select({ ios: 'appl_...', android: 'goog_...' }),
  useAmazon: false,
  observerMode: false, // true only if you handle purchases yourself
});

// Entitlement check
const customerInfo = await Purchases.getCustomerInfo();
const isPro = customerInfo.entitlements.active.pro !== undefined;

Server-side verification: Call RevenueCat REST API /v1/subscribers/{app_user_id} from your backend to validate entitlements before granting access.

Apple StoreKit 2 / App Store Connect

  • Test with StoreKit Configuration File (.storekit) in Xcode for local testing without network.
  • Transaction.currentEntitlements (iOS 15+) for on-device verification.
  • Server-side receipt verification: POST https://api.revenuecat.com/v1/subscribers/{id} or Apple /verifyReceipt endpoint (deprecated, use App Store Server API).

Performance & Profiling

ToolPurposeCommand / Setup
react-native-performance (Sentry)Vitals, slow frames, TTInpm i @sentry/react-native + Sentry.init({ dsn, enablePerformance: true })
why-did-you-renderUnnecessary re-rendersnpm i @welldone-software/why-did-you-render + patch React in App.js
hermes-profile / chrome://tracingCPU profilingadb shell setprop debug.hermes.profiler 1 → load trace in Chrome
metro-bundle-analyzerBundle sizenpx metro-bundle-analyzer → open HTML report
react-native-reanimated profilerWorklet execution timeReanimated.setProfilingEnabled(true) in dev

Quick profile: adb shell setprop debug.hermes.profiler 1, reproduce scenario, adb shell setprop debug.hermes.profiler 0, pull trace: adb shell run-as com.myapp cat files/.hermes/*.trace | npx metro-symbolicate > profile.json, open chrome://tracing → Load.

CI/CD & Automation Signals

GitHub Actions Artifact Upload

- name: Upload Gradle scan
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: gradle-scan-${{ github.run_id }}
    path: android/build/reports/scan/

- name: Upload Xcode result bundle
  uses: actions/upload-artifact@v4
  if: always()
  with:
    name: xcode-result-${{ github.run_id }}
    path: build.xcresult
./gradlew assembleRelease --no-daemon --scan -PreactNativeArchitectures=arm64-v8a
# Output: "Publishing build scan... https://gradle.com/s/xxxx"

Xcode Result Bundle

xcodebuild -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -destination generic/platform=iOS \
  -resultBundlePath build.xcresult \
  -quiet
# Parse with xcparse or xcresulttool

EAS Build Logs

eas build:list --platform=android --limit=5
eas build:view <build-id> --logs

Fastlane Scan/Gym Output

# Fastfile
lane :test do
  scan(
    workspace: "MyApp.xcworkspace",
    scheme: "MyApp",
    result_bundle: true,
    output_directory: "fastlane/test_output"
  )
end

Rollback & Recovery (Expanded)

Expo Updates Rollback

# List recent updates
npx expo-updates:list
# Rollback to previous
npx expo-updates:rollback
# Or publish a new update with previous bundle
eas update --branch production --message "Rollback to v1.2.3"

EAS Build Rebuild from Cache

eas build --profile production --platform android --clear-cache
# Or rebuild specific commit
eas build --profile production --platform android --commit <sha>

Git Bisect Automation

# Automated bisect for regression
git bisect start HEAD <good-tag>
git bisect run npm ci && npm test
git bisect reset

Native Module Rebuild Isolation

# Reinstall JS deps without native rebuild scripts
npm ci --ignore-scripts
# Then rebuild native only
cd android && ./gradlew clean assembleDebug
cd ../ios && pod install --repo-update

Realistic Technical Scenario

Scenario: Android release build crashes on startup with java.lang.UnsatisfiedLinkError: libhermes.so after upgrading RN 0.73→0.74 and adding react-native-reanimated 3.x.

1. Inventory Capture

npx react-native info
# Output shows: RN 0.74.2, Hermes enabled, AGP 8.2.2, JDK 21, Reanimated 3.10.0

2. Log Correlation

# logcat for native crash
adb logcat -s ReactNative:V System.err:V -v threadtime > crash.log
# Hermes crash dump
adb shell run-as com.myapp cat files/.hermes/*.log | npx metro-symbolicate > hermes-crash.txt

Key logcat lines:

E System.err: java.lang.UnsatisfiedLinkError: dlopen failed: library "libhermes.so" not found
E System.err: at com.facebook.soloader.SoLoader.loadLibrary(SoLoader.java:...)
E ReactNativeJS: Fatal: TurboModuleRegistry.getEnforcing(...): 'NativeReanimated' not found

3. Narrow Scope

  • Disable Reanimated: Comment out import 'react-native-reanimated'; and Reanimated usage.
  • Rebuild: cd android && ./gradlew clean assembleRelease --stacktrace.
  • Result: App launches → Reanimated is the trigger.

4. Root Cause & Fix

Cause: RN 0.74 requires react-native-reanimated 3.10+ with Fabric enabled. libhermes.so missing because hermes_enabled=true but newArchEnabled=false in gradle.properties — Hermes JSI bindings for TurboModules not generated. Also soLoader config in android/app/build.gradle missing jniLibs for Hermes.

Fix (android/gradle.properties):

hermes_enabled=true
newArchEnabled=true # Required for TurboModules/Fabric

Fix (android/app/build.gradle):

dependencies {
  // Ensure Hermes JSI libs are packaged
  implementation "com.facebook.react:hermes-engine:+"
}

Fix (ios/Podfile):

use_frameworks! :linkage => :static
# Ensure Reanimated pods use Fabric
pod 'React-RCTFabric', :path => '../node_modules/react-native/React/Fabric'

Run:

cd android && ./gradlew clean assembleRelease --no-daemon --scan -PreactNativeArchitectures=arm64-v8a
cd ../ios && pod install --repo-update

5. Verify

  • Release APK installs and launches on device.
  • adb logcat -s ReactNative:V shows TurboModuleRegistry.getEnforcing: NativeReanimated success.
  • Reanimated animations run on UI thread (no JS thread drops).

6. Rollback Plan

# Revert gradle.properties
git checkout HEAD -- android/gradle.properties
# Revert Reanimated version
npm install [email protected] # last version compatible with RN 0.73
# Rebuild
cd android && ./gradlew clean assembleRelease

Decision: If timeline critical, rollback RN to 0.73.3 and Reanimated to 3.6.2; schedule RN 0.74 migration with dedicated QA.

Conclusion

React Native troubleshooting is most effective when you stabilize the environment, gather high-signal logs, and make the smallest safe change. With a clear inventory, predictable debug and release settings, and a disciplined verify-and-rollback routine, most problems become straightforward to isolate and fix. Use the inventory commands to establish a baseline, the log commands to observe real behavior across Metro, logcat, OSLog, and Hermes crash dumps, and the example-driven fixes here to resolve common issues with Metro resolution, Android and iOS builds, REST networking, Google Play Billing, Stripe, and RevenueCat integrations. Keep changes small, verify locally on both platforms, and roll back quickly if needed. Over time, this approach shortens incident duration and makes outcomes reliable across your team.

Related Research

Article Quality Score

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