Common Crashes in React Native Apps and How to Fix Them
React Native apps crash for a different set of reasons than fully native apps. The JS engine, bridge, and native modules introduce failure modes that pure Kotlin or Swift apps do not see. This guide c
React Native apps crash for a different set of reasons than fully native apps. The JS engine, bridge, and native modules introduce failure modes that pure Kotlin or Swift apps do not see. This guide covers the recurring crash categories, how to diagnose them, and the fixes that actually work.
Why RN crashes differently
Every RN app has at least four layers: JavaScript, the bridge (or JSI in newer versions), native modules, and platform code. Crashes can originate in any layer, and symptom (the stack trace) often points at a layer different from the root cause.
Common crash categories
1. JS exceptions in async code
fetch() throws, no catch, Promise rejects uncaught. On iOS, leads to redbox in dev, silent in production. On Android, may propagate and crash.
Fix:
try { const data = await api.fetch(); }
catch (err) { Sentry.captureException(err); showUserError(); }
Global unhandled rejection handler:
ErrorUtils.setGlobalHandler((err, isFatal) => { /* log, navigate to error screen */ });
2. Null / undefined access in render
Classic: props.user.name when user is undefined during loading.
Fix:
- Optional chaining everywhere:
props.user?.name ?? "Guest" - Proper loading states before rendering data-dependent components
3. State update on unmounted component
setState called after componentDidUnmount. Warning in older RN, crash in some flows.
Fix:
- Hooks:
useEffectwith cleanup - Class:
isMountedflag or AbortController on fetch
4. Bridge serialization failures
Passing non-serializable values (functions, circular refs, native objects) over the bridge. Crashes the JS runtime.
Fix: Only primitives, strings, arrays, and plain objects cross the bridge. Use IDs, not references.
5. Native module exceptions
Native module throws an uncaught exception. Crashes the app process.
Fix: Every native module method wrapped in try/catch, reject Promise with useful error, not unhandled exception.
6. OOM from large JS arrays
Holding 100k items in state. JS heap fills, garbage collector thrashes, eventually OOM.
Fix:
- Virtualize lists (FlatList with windowSize, not ScrollView)
- Paginate — never hold all records in memory
- Normalize state — IDs and lookups, not full objects
7. Image decoding OOM
Loading many full-res images at once. Native image system cannot decode, crashes.
Fix:
- FastImage or React Native Image with resizing
- Thumbnails in lists, full image on tap only
- Explicit
cachepolicy
8. Version mismatches
AndroidX / Gradle / RN version / Kotlin version all need to align. Mismatch → build fails or runtime crashes.
Fix: Pin all versions. Use a setup script. Never upgrade RN without upgrading Gradle, AndroidX, Kotlin.
9. Linking broken after pod install / gradle sync
Native module added to package.json but not properly linked. Import at JS layer works (returns undefined or throws on call).
Fix: Autolinking handles this on RN 0.60+. If using older RN, run react-native link. Always test on a fresh clone.
10. Memory leaks in long-lived listeners
Event emitter subscription never removed. Over time, multiple listeners fire for one event.
Fix:
useEffect(() => {
const sub = emitter.addListener("event", handler);
return () => sub.remove();
}, []);
Debugging
Dev
- Redbox screen with JS stack trace
- Sentry / Bugsnag in production gives near-JS-source stacks
adb logcat -s ReactNativeJSfor Android- Xcode console for iOS
Production
- Crashlytics / Sentry integration (JS + native + NDK)
- Symbolication for native stacks
- Source maps for JS stacks
Crash not a JS crash but RN still suspect
Check:
- Gradle / Pod cache clean, reinstall
- Different architecture (arm64 vs x86_64)
- Hermes vs JSC
- ProGuard / R8 stripping required classes (config RN libraries)
Fabric / New Architecture
New architecture (Fabric + TurboModules) changes the runtime from JS-bridge to JSI-native. Several crash modes change:
- Bridge serialization crashes → gone (JSI direct calls)
- Native module exceptions → still possible, different stack
- Initialization crashes → new shapes (invariant violations)
Upgrading to Fabric requires testing; crash patterns will shift even if overall rate improves.
How SUSA catches RN crashes
SUSA monitors logcat (Android) and unified log (iOS) during exploration. Any crash during a tap / swipe / input is attributed to that step. Report includes:
- Crash stack trace
- Screen name
- Action that triggered it
- Screenshot before and after
- Logcat / syslog window around the crash
For RN specifically, SUSA catches:
- Redbox appearances in dev builds
- Native crashes with RN frames in the stack
- JS-level crashes surfaced through Crashlytics NDK integration
The adversarial persona deliberately fires rapid actions, invalid input, and unusual state transitions — exactly the patterns that trigger bridge serialization and null-access crashes.
susatest-agent test rnapp.apk --persona adversarial --steps 200
Preventing crashes going forward
- TypeScript — catches half of null / undefined access bugs at compile time
- Global error handler — always send to crash reporter, show graceful fallback
- E2E tests (Detox) — cover the critical flows that must never crash
- SUSA exploration — finds the crashes your tests did not cover
- Gradual rollouts — 1% → 5% → 25% → 100% with crash-rate monitoring
Crashes are tractable. Most RN crash rates can reach < 1% with systematic work. The "99% crash-free" rate Google recommends is achievable and worth the effort.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free