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

The Complete Guide to React Native Crash Debugging

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


CrashLikely CauseFix
Cannot read property 'X' of undefinedMissing null checkOptional chaining: obj?.prop?.x
Maximum update depth exceededInfinite render loopCheck useEffect dependency arrays
VirtualizedList: You have a large listMissing keyExtractorAdd stable keyExtractor prop
Text strings must be rendered within a <Text>Conditional whitespace in JSXWrap all text in <Text> components
White screen on launch (production only)Uncaught error before ErrorBoundaryMove BugsPulse.init() to the very first line
Crash after background/foreground cycleAppState not cleaned upRemove 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.