React Native

React Native Error Boundaries and Crash Reporting Strategy for Production

5 min read by DebuggedIt

Quick answer

In React Native, error boundaries catch rendering errors in a part of the component tree and show a fallback UI, but they do not catch errors in event...

In React Native, error boundaries catch rendering errors in a part of the component tree and show a fallback UI, but they do not catch errors in event handlers, async code, or native crashes. A production strategy layers several mechanisms: boundaries for rendering, a global JavaScript error handler for the rest, and a crash reporting service for native failures, all sending context that helps you reproduce problems.

What Do Error Boundaries Catch and Miss?

An error boundary is a class component that implements lifecycle methods for handling errors thrown during rendering, in lifecycle methods, and in constructors of its children. When an error occurs, it renders a fallback instead of crashing the whole app.

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    reportError(error, { componentStack: info.componentStack });
  }

  render() {
    return this.state.hasError ? this.props.fallback : this.props.children;
  }
}

Boundaries do not catch errors thrown in event handlers, in asynchronous code such as promises and timers, or in the boundary itself. They also cannot catch crashes in native code. Relying on boundaries alone leaves large gaps, which is why you need other layers.

The component stack provided to the boundary is especially valuable. It tells you which components were rendering when the failure happened, which is often more helpful than the JavaScript stack alone when you are trying to reproduce a bug reported by a user.

Where Should You Place Error Boundaries?

A single boundary at the root prevents a white screen, but it throws away all the user's context when anything fails. Finer-grained boundaries limit the damage. If a recommendation widget fails, the rest of the screen should keep working.

  • Root boundary: the last line of defense, with a full-screen fallback and a restart option.
  • Screen boundaries: isolate each screen, so a failure does not take down navigation.
  • Feature boundaries: wrap risky or third-party components, such as maps, charts, and rich text.

Design fallbacks to be useful. Offer a retry button that resets the boundary state, and avoid fallbacks that depend on the same data or component that just failed. Keep the fallback simple, since it also runs in a broken state.

How Do You Handle Errors Outside Rendering?

Errors in event handlers and async code need explicit handling. Wrap important operations in try and catch blocks, and handle rejected promises at the call site. For anything that slips through, install global handlers.

React Native provides a way to set a global JavaScript error handler, and runtimes expose hooks for unhandled promise rejections. The exact APIs and behavior differ across versions and JavaScript engines, so check the documentation for yours. A global handler is a safety net for reporting, not a replacement for handling errors close to where they happen.

  • Distinguish fatal errors, which crash the app, from non-fatal ones that you can log and survive.
  • Show friendly messages for expected failures such as network errors, and reserve reporting for unexpected ones.
  • Do not swallow errors silently. At least log them with context.

How Should Crash Reporting Be Set Up?

A reporting service collects JavaScript errors and native crashes in one place, with stack traces, device information, and app version. To make JavaScript stack traces readable in production, upload source maps for every release, and native symbol files for native crashes, matching the exact build.

Add context to every report. Include the release version, the user's anonymous identifier, the current screen, and a trail of recent actions, often called breadcrumbs. Avoid sending personal data or secrets, and scrub sensitive values from breadcrumbs and network logs.

  • Tag reports with the release and build number, and compare crash-free rates between releases.
  • Set up alerts for new issues and spikes after a rollout.
  • Deduplicate and group errors, and assign ownership so reports do not pile up unread.
  • Respect user privacy settings and applicable regulations when collecting diagnostics.

Treat the crash dashboard as part of your release process. Review it after every rollout, decide whether to pause or continue the release based on the numbers, and feed the most frequent crashes back into your planning, so stability work gets scheduled instead of ignored.

Frequently Asked Questions

Do error boundaries catch native crashes?

No. Native crashes need a crash reporting tool with native support.

Why is my production stack trace unreadable?

Source maps are missing or do not match the build. Upload them for each release.

Should I wrap every component in an error boundary?

No. Place them at the root, around screens, and around risky features.

Do error boundaries catch errors in async code?

No. Handle those with try and catch, promise handlers, and global handlers.

Summary

Layer error boundaries, local error handling, global handlers, and native crash reporting. Upload source maps, add context, and monitor each release. Consult the documentation of React Native and your reporting tool for version-specific setup.