Handling Errors and Crashes
1. Using Error Boundaries
| Property | Description | Usage |
|---|---|---|
| ErrorBoundary | Component that catches JavaScript errors anywhere in its child component tree | class ErrorBoundary extends React.Component { ... } |
| componentDidCatch | Method called when an error is caught | componentDidCatch(error, errorInfo) |
| fallback | Rendered when an error occurs | <ErrorBoundary fallback="<h1>Something went wrong</h1>"> |
Example: Basic Error Boundary
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, errorInfo) {
console.error("Caught an error:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Note: Error boundaries only catch errors in the component tree below them. They do not catch errors in event handlers or asynchronous code like setTimeouts.
2. Implementing Global Error Handlers
Example: Setting up a global error handler
import { ErrorBoundary } from 'react-native-error-boundary';
const App = () => {
return (
<ErrorBoundary
onError={(error, errorInfo) => {
console.error('Global Error:', error);
console.error('Error Info:', errorInfo);
}}
>
<MyApp />
</ErrorBoundary>
);
};
| Handler Type | Description | Usage |
|---|---|---|
| ErrorBoundary | Component that catches JavaScript errors below it in the tree | Wrap root or major sections of your app |
| onError | Callback function when an error is caught | Log errors or send to crash reporting service |
Note: Error boundaries only catch errors in the rendering phase, lifecycle methods, and constructors of child components.
Example: Logging errors to a service
const logErrorToService = (error, errorInfo) => {
fetch('https://api.example.com/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: error.toString(),
stack: error.stack,
componentStack: errorInfo.componentStack,
}),
});
};
const App = () => {
return (
<ErrorBoundary onError={logErrorToService}>
<MyApp />
</ErrorBoundary>
);
};
3. Catching Promise Rejections
| Method | Description | Usage |
|---|---|---|
| Promise.catch() | Catches rejection reason of a Promise | promise.catch((error) => { ... }); |
| try/catch with async/await | Handles asynchronous errors in a structured way | |
Example: Handling Promise Rejection
const fetchData = () => {
return fetch('/api/data')
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.json();
})
.catch(error => {
console.error('Fetch failed:', error);
return null;
});
};
4. Logging Errors to Services
| Service | Features | Notes |
|---|---|---|
| Sentry | captureException, captureMessage |
Full error tracking, breadcrumbs, performance monitoring |
| Bugsnag | notify, leaveBreadcrumb |
Real-time error tracking, crash reporting, dashboard |
| LogRocket | sessionURL, replayURL |
Session replay, error tracking, user behavior analytics |
| Rollbar | reportException, reportMessage |
Real-time error tracking, deployment tracking, user impact |
Example: Sentry Integration
import * as Sentry from '@sentry/react-native';
Sentry.init({
dsn: 'YOUR_DSN_HERE',
});
// Log an exception
try {
throw new Error('Something went wrong');
} catch (error) {
Sentry.captureException(error);
}
Example: Bugsnag Integration
import Bugsnag from '@bugsnag/react-native';
Bugsnag.start({
apiKey: 'YOUR_API_KEY'
});
// Notify Bugsnag of an error
Bugsnag.notify(new Error('Something went wrong'));
Note: Ensure that sensitive data is not sent to error logging services. Use
beforeSend hooks or filtering to sanitize logs.
5. Handling Native Crashes
| Crash Type | Description | Handling Strategy | Tools |
|---|---|---|---|
| JavaScript Exceptions | Uncaught JavaScript errors in React Native | Use try/catch blocks and Errors for graceful handling |
errorBoundary, console.error |
| Native Crashes (iOS/Android) | Crashes in native code (e.g., memory issues, unhandled exceptions) | Implement crash reporting and recovery logic using native crash handlers | Crashlytics, Bugsnag, Sentry |
| Memory Leaks | Improper memory management causing app instability | Monitor memory usage with tools like Flipper or React Native Debugger | Flipper, React DevTools |
Example: Implementing Error Boundaries in React Native
import React from 'react';<br><br>class ErrorBoundary extends React.Component {<br> constructor(props) {<br> super(props);<br> this.state = { hasError: false };<br> }<br><br> static getDerivedStateFromError(error) {<br> // Update state so the next render will show the fallback UI.<br> return { hasError: true };<br> }<br><br> componentDidCatch(error, errorInfo) {<br> // You can also log the error to an error reporting service<br> console.error("Error caught by boundary:", error, errorInfo);<br> }<br><br> render() {<br> if (this.state.hasError) {<br> // You can render any fallback UI<br> return <h1>Something went wrong.</h1>;<br> }<br><br> return this.props.children;<br> }<br>}<br><br>export default ErrorBoundary;
Note: Error boundaries in React Native only catch errors in the component tree below them. They do not catch errors in event handlers or asynchronous code like setTimeout or requestAnimationFrame.
Example: Using Sentry for Native Crash Reporting
import * as Sentry from '@sentry/react-native';<br><br>Sentry.init({<br> dsn: 'YOUR_DSN_HERE',<br> enableAutoSessionTracking: true,<br>});<br><br>// In your app entry point<br>Sentry.captureException(new Error('Native crash example'));
Native Crash Handling Summary
- Use
ErrorBoundaryfor JavaScript errors - Integrate crash reporting tools like Sentry or Bugsnag for native crashes
- Monitor memory usage to prevent leaks
- Implement proper logging and error tracking
6. Using Try-Catch Blocks
| Feature | Description | Usage |
|---|---|---|
| try...catch | Standard JavaScript error handling mechanism | Wrap code that might throw an error in a try block and handle it in catch |
| Error object | Represents an error with properties like name, message, and stack | Access error details in the catch block |
Example: Basic Try-Catch Usage
try {
const result = JSON.parse(invalidJson);
} catch (error) {
console.error("Parsing failed:", error.message);
}
Example: Handling Asynchronous Errors
try {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
} catch (error) {
console.error("Network error:", error.message);
}
7. Implementing Crash Reporting
Example: Setting up crash reporting with Sentry
import * as Sentry from '@sentry/react-native';
Sentry.init({
dsn: 'YOUR_DSN_HERE',
tracesSampleRate: 1.0,
});
// Log a crash manually
Sentry.captureException(new Error('Something went wrong'));
| Property | Description | Values |
|---|---|---|
| dsn | Unique identifier for your project in Sentry | string |
| tracesSampleRate | Percentage of transactions to send to Sentry | number (0.0 - 1.0) |
| enableAutoSessionTracking | Automatically track session duration | true | false |
Note: Ensure you have the correct DSN from your Sentry project settings.
Warning: Crash reports may contain sensitive user data. Review and sanitize logs before sending to third-party services.
8. Debugging Production Errors
Example: Setting up Sentry for React Native
// Install Sentry
npm install @sentry/react-native
// Initialize Sentry in your app
import * as Sentry from '@sentry/react-native';
Sentry.init({
dsn: 'YOUR_DSN_HERE',
enableAutoSessionTracking: true,
});
// Capture an exception
Sentry.captureException(new Error('Something went wrong'));
| Feature | Description | Notes |
|---|---|---|
| Sentry.init() | Initializes the Sentry SDK | Must be called before any error occurs |
| captureException() | Captures and sends exception to Sentry | Can be called with any Error object |
| captureMessage() | Sends a message to Sentry | Useful for logging non-exception events |
Note: Sentry requires a valid DSN (Data Source Name) to send errors. Make sure to configure it correctly in your production environment.
Warning: Do not send sensitive user data to Sentry. Ensure all PII (Personally Identifiable Information) is stripped before sending.
Example: Custom Error Boundary in React Native
import React from 'react';
import { Alert } from 'react-native';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Caught an error:', error, errorInfo);
// Send to crash reporting service
}
render() {
if (this.state.hasError) {
return (
<Alert
title="Something went wrong"
message="Please restart the app"
/>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
Best Practices for Production Error Debugging
- Use a dedicated error reporting service like Sentry or Bugsnag
- Implement custom error boundaries to gracefully handle crashes
- Log errors with context (user actions, app state)
- Filter out sensitive data before sending to error reporting services
- Set up alerts for critical errors in production
9. Handling Unhandled Exceptions
| Exception Type | Description | Handling Strategy |
|---|---|---|
| JavaScript Exceptions | Thrown by JavaScript runtime, e.g., TypeError, ReferenceError |
Use try...catch blocks or global error handlers like errorBoundary |
| Native Exceptions | Thrown by native modules or platform-specific code (iOS/Android) | Implement NativeModule error handling or use platform-specific crash reporting tools |
Example: Global Error Handler in React Native
import { ErrorUtils } from 'react-native';<br><br>ErrorUtils.setGlobalHandler((error, isFatal) => {<br> console.log('Global Error:', error);<br> if (isFatal) {<br> // Log crash report<br> console.log('Fatal error occurred');<br> } else {<br> // Handle recoverable error<br> console.log('Recoverable error');<br> }<br>});
Note:
ErrorUtils.setGlobalHandler is a React Native-specific API for catching unhandled JavaScript errors.
Warning: Do not log sensitive user data in global error handlers to avoid privacy issues.
10. Creating Error Fallback UI
Example: Basic Error Boundary Component
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error("Error caught by boundary:", error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '20px', backgroundColor: '#ffebee' }}>
<h2>Something went wrong.</h2>
<p>We're sorry, but an error occurred.</p>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;
| Property | Description | Notes |
|---|---|---|
| static getDerivedStateFromError | Updates state when an error is thrown in a child component | Must return new state or null |
| componentDidCatch | Logs error details for debugging | Called after state update; not called for errors in boundary itself |
| render | Controls fallback UI rendering | Return null to hide error; return custom UI otherwise |
Note: Error boundaries do not catch errors in event handlers, asynchronous code (e.g., setTimeout or requestAnimationFrame), server-side rendering, or errors thrown in the error boundary itself.
Warning: Error boundaries only work for components in the rendering tree below them. They do not catch errors inside themselves or their children's asynchronous operations.