AI-powered crash analysis is now available on all plans — including Free.Read the crash analysis guide

The Complete React Native Crash Debugging Guide

NFNourin Mahfuj Finick··10 min read

Crashes are the fastest way to lose users. A 1% crash rate sounds small until you do the math — on an app with 100,000 daily active users, that's 1,000 crashes every single day. Unlike web apps where you can open DevTools and reproduce issues immediately, mobile crashes demand deliberate instrumentation to diagnose and fix.

This guide walks through the complete React Native crash debugging workflow: the types of crashes you'll encounter, how to capture them properly, how to read stack traces, and how to use session context to understand exactly what drove your user into a crash state.

The 5 Types of React Native Crashes

Not all crashes are equal. Understanding the type tells you where to look first.

1. JavaScript exceptions

The most common type. An unhandled TypeError, ReferenceError, or a thrown error that wasn't caught somewhere in your component tree. React Native's default behavior is to show a red screen in development and a white screen (or silent crash) in production.

// Classic example — accessing a property on undefined
const userName = user.profile.name; // crashes if user or profile is null

2. Unhandled promise rejections

Async code that throws without a .catch() or try/catch around an await. These are silent killers — they don't always crash the app immediately, but they leave it in a broken state.

// Missing error handling
async function loadData() {
  const response = await fetch('/api/data'); // throws on network error
  return response.json();
}

3. Native module bridge crashes

When a native module (written in Java/Kotlin or Objective-C/Swift) throws an exception that crosses the bridge, React Native can't recover gracefully. These show up as cryptic native stack traces with no JavaScript context.

4. Out-of-memory (OOM) kills

Android and iOS will kill your process when it uses too much memory. There's no exception thrown and no stack trace — the process simply dies. Common causes: large image lists without recycling, memory leaks from uncleared intervals, and holding references in closures.

5. ANR (Application Not Responding) on Android

When the main thread is blocked for more than 5 seconds, Android kills the app with an ANR. Caused by synchronous work on the main thread — file I/O, heavy computation, or blocking network calls.

Why Crashes Are So Hard to Debug

Android fragmentation

Android runs on thousands of device/OS combinations. A crash on a Samsung Galaxy A12 running Android 10 with a custom OEM memory manager may never reproduce on your Pixel 7 running Android 14. Without crash data from production devices, you're guessing.

Minified stack traces

Production JavaScript bundles are minified. A raw crash report looks like:

TypeError: Cannot read property 'n' of undefined
  at e (index.android.bundle:1:923847)
  at t (index.android.bundle:1:923901)
  at r (index.android.bundle:1:924012)

Without source maps, these are useless. You need to upload your source maps to your crash reporter at build time.

Missing context

A stack trace tells you where the crash happened, not why. The why is in what happened before: which screen was the user on? Did a previous API call return a 500? Was the app in the background? Without that context, you're staring at a line number with no story around it.

Setting Up Crash Reporting in React Native

Install the SDK

npm install @bugspulse/react-native
# or
yarn add @bugspulse/react-native

For bare React Native, link the native module:

npx pod-install

Initialize early in your app

Call BugsPulse.init() at the very top of your entry file, before any other imports that might throw:

// index.js or App.tsx
import BugsPulse from '@bugspulse/react-native';
 
BugsPulse.init({
  apiKey: 'bp_your_project_key',
  environment: __DEV__ ? 'development' : 'production',
  sessionReplay: true,
  captureNetworkRequests: true,
});

Add an ErrorBoundary for JS crashes

React's ErrorBoundary catches render-phase errors and lets you log them before showing a fallback UI:

import React from 'react';
import BugsPulse from '@bugspulse/react-native';
 
class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean }
> {
  state = { hasError: false };
 
  static getDerivedStateFromError() {
    return { hasError: true };
  }
 
  componentDidCatch(error: Error, info: React.ErrorInfo) {
    BugsPulse.captureException(error, {
      componentStack: info.componentStack,
    });
  }
 
  render() {
    if (this.state.hasError) {
      return <FallbackScreen />;
    }
    return this.props.children;
  }
}
 
// Wrap your entire app
export default function App() {
  return (
    <ErrorBoundary>
      <NavigationContainer>
        {/* ... */}
      </NavigationContainer>
    </ErrorBoundary>
  );
}

Capture unhandled promise rejections

// In your init block, after BugsPulse.init()
const originalHandler = global.Promise;
// BugsPulse automatically patches Promise — just make sure init runs first

BugsPulse's SDK hooks into the global unhandled rejection handler automatically during init(). No manual wiring needed.

Reading a React Native Crash Report

Once crashes are flowing into your dashboard, here's how to triage them efficiently.

The crash fingerprint

A good crash reporter groups crashes by a normalized fingerprint — the stack trace with memory addresses stripped. This lets you see "TypeError: Cannot read property 'id' of undefined in UserProfileScreen" happening 47 times across 23 users, rather than 47 individual reports.

Prioritize crashes by user impact (unique users affected), not raw count. One crash happening 500 times to the same user is less urgent than a crash happening 30 times across 30 different users.

What to look for in session context

Before jumping to the stack trace, look at what the user was doing:

  • Last 3 screens — was the crash always preceded by a specific navigation?
  • Last network request — did a 401, 500, or timeout happen right before?
  • Device and OS — is this crash isolated to Android 9 or Samsung devices?
  • App version — was this introduced in the last release?
  • Session duration — does it crash after 5 seconds (startup crash) or after 15 minutes (memory leak)?

Symbolicated vs raw stack traces

If your stack traces look like minified garbage, you need to upload source maps. For React Native:

# Generate the bundle and source map
npx react-native bundle \
  --platform android \
  --dev false \
  --entry-file index.js \
  --bundle-output android/app/src/main/assets/index.android.bundle \
  --sourcemap-output android/app/src/main/assets/index.android.bundle.map

Upload the .map file to your crash reporter along with the build version. BugsPulse automatically symbolicates incoming stack traces against the uploaded map.

Debugging Android-Specific Crashes

Null pointer exceptions from native modules

A common pattern: a React Native component mounts, calls a native module method, and the native module crashes because a required Android permission wasn't granted.

java.lang.NullPointerException: Attempt to invoke virtual method
  on a null object reference at com.example.CameraModule.openCamera

Fix: Always check permissions before calling native modules. Use PermissionsAndroid.check() before camera/location/contacts access.

Android 12+ background activity restrictions

Apps targeting Android 12+ can't start activities from the background. If you're triggering navigation from a push notification handler, you'll hit a BackgroundActivityStartNotAllowed exception.

Fix: Use PendingIntent with the correct flags and ensure your deep link handler runs on the main thread.

Different behavior across React Native versions

React Native's new architecture (Fabric + TurboModules) changed how native crashes propagate. If you're on RN 0.71+, native module exceptions are now wrapped differently. Check your crash reporter for exceptions with TurboModule in the trace.

Debugging iOS-Specific Crashes

Memory warnings before OOM

iOS sends applicationDidReceiveMemoryWarning before killing the process. You can log a breadcrumb when this fires to confirm OOM kills in your crash reports:

import { AppState } from 'react-native';
import BugsPulse from '@bugspulse/react-native';
 
// In your App component
useEffect(() => {
  const sub = AppState.addEventListener('memoryWarning', () => {
    BugsPulse.addBreadcrumb({
      message: 'Memory warning received',
      level: 'warning',
    });
  });
  return () => sub.remove();
}, []);

EXC_BAD_ACCESS crashes

These are usually use-after-free bugs in native code — a native object that was deallocated while a JavaScript reference still existed. They're rare with modern React Native but can occur in older native modules.

If you see EXC_BAD_ACCESS (SIGSEGV) or EXC_BAD_ACCESS (SIGBUS) in your crash reports, check for:

  • Native modules manually managing memory
  • Accessing self in an Objective-C block after the owner was deallocated
  • Race conditions in native thread callbacks

The Debugging Workflow

1. Triage by impact

Each week, sort crashes by unique users affected. Fix the top 3. Crashes affecting 1 user on an obscure device can wait.

2. Group by session path

Look at 5–10 sessions that ended in the same crash. If 8 out of 10 crashed after navigating to CheckoutScreen following a network error on /cart, that's your bug — the crash isn't in CheckoutScreen, it's in how you handle the error state returned by /cart.

3. Reproduce locally with the session data

Armed with: device model, OS version, app state, and the sequence of events — you can now attempt to reproduce. Set your test device to the same OS version, navigate the same path, and trigger the same network condition (use a proxy like Charles or mitmproxy to inject errors).

4. Fix and verify with a canary release

Deploy the fix to a small percentage of users first (5–10%). Monitor your crash dashboard for 24 hours. If the crash rate for that cohort drops to zero, you've confirmed the fix. If it persists, the root cause is somewhere else.

Common Crash Patterns and Quick Fixes

Crash Likely Cause Fix
Cannot read property 'X' of undefined Missing null check Optional chaining: obj?.prop?.x
Maximum update depth exceeded Infinite render loop Check useEffect dependency arrays
VirtualizedList: You have a large list Missing keyExtractor Add stable keyExtractor prop
Text strings must be rendered within a <Text> Conditional whitespace in JSX Wrap all text in <Text> components
White screen on launch (production only) Uncaught error before ErrorBoundary Move BugsPulse.init() to the very first line
Crash after background/foreground cycle AppState not cleaned up Remove listeners in useEffect cleanup

Summary

React Native crash debugging comes down to three things: capture crashes with enough context before they become patterns, symbolicate your stack traces so they're readable, and use session data to reconstruct the exact story of what the user was doing. A raw stack trace tells you where the code failed. The session tells you why.

Set up instrumentation once, and your crashes become a prioritized backlog instead of a mystery.

Related reading

Try BugsPulse free — event-based session replay, AI-powered crash analysis, and native crash capture for React Native and Flutter. 500 sessions/month, no credit card required. Get started at bugspulse.com