React Error Boundaries
Catching and handling errors in React components
🛡️ What are Error Boundaries?
Error Boundaries are React components that catch JavaScript errors in their child component tree, log errors, and display a fallback UI instead of crashing the entire application.
import { Component } from 'react';
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
Error Boundary Concepts
Error Catching
Catches errors in child components
componentDidCatch(error, info)
Fallback UI
Shows alternative UI when error occurs
return <ErrorMessage />;
Error Logging
Log errors for debugging
console.error(error, errorInfo);
Component Tree
Protects specific parts of app
<ErrorBoundary>
<App />
</ErrorBoundary>
🔹 Basic Error Boundary
Create a simple error boundary class component. Error boundaries must be class components because they use lifecycle methods not available in function components.
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so next render shows fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log error to error reporting service
console.error('Error caught by boundary:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h1>😕 Oops! Something went wrong.</h1>
<p>We're sorry for the inconvenience.</p>
</div>
);
}
return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
);
}
Output (When Error Occurs):
😕 Oops! Something went wrong.
We're sorry for the inconvenience.
🔹 Error Boundary with Reset
Add a reset button to recover from errors. This allows users to try again without refreshing the entire page.
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
console.error('Error:', error);
console.error('Error Info:', errorInfo);
}
resetError = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h1>⚠️ Something went wrong</h1>
<p>{this.state.error?.message}</p>
<button
onClick={this.resetError}
style={{
padding: '10px 20px',
background: '#007cba',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
Try Again
</button>
</div>
);
}
return this.props.children;
}
}
Output (With Reset Button):
⚠️ Something went wrong
Cannot read property 'name' of undefined
🔹 Multiple Error Boundaries
Use multiple error boundaries to isolate errors to specific parts of your app. This prevents one broken component from crashing the entire application.
function App() {
return (
<div>
<Header />
<ErrorBoundary>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary>
<MainContent />
</ErrorBoundary>
<ErrorBoundary>
<Footer />
</ErrorBoundary>
</div>
);
}
// If MainContent crashes, Sidebar and Footer still work!
🔹 Custom Error Boundary with Logging
Create an error boundary that sends errors to a logging service. This helps you track and fix production errors.
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Send to error tracking service
this.logErrorToService(error, errorInfo);
}
logErrorToService = (error, errorInfo) => {
// Example: Send to your logging service
fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: error.toString(),
errorInfo: errorInfo.componentStack,
timestamp: new Date().toISOString()
})
});
};
render() {
if (this.state.hasError) {
return this.props.fallback || <h1>Error occurred</h1>;
}
return this.props.children;
}
}
// Usage with custom fallback
function App() {
return (
<ErrorBoundary fallback={<CustomErrorPage />}>
<MyApp />
</ErrorBoundary>
);
}
🔹 What Error Boundaries Catch
Error boundaries have limitations on what they can catch:
✅ Error Boundaries CATCH:
- Rendering errors: Errors during component render
- Lifecycle methods: Errors in lifecycle methods
- Constructor errors: Errors in component constructors
- Child component errors: Errors in the component tree below
❌ Error Boundaries DON'T CATCH:
- Event handlers: Use try-catch in event handlers
- Async code: setTimeout, promises, async/await
- Server-side rendering: SSR errors
- Errors in error boundary itself: Can't catch its own errors
🔹 Handling Event Handler Errors
Since error boundaries don't catch event handler errors, use try-catch blocks:
function MyComponent() {
const [error, setError] = useState(null);
const handleClick = () => {
try {
// Code that might throw an error
riskyOperation();
} catch (error) {
// Handle error manually
setError(error.message);
console.error('Error in event handler:', error);
}
};
if (error) {
return <div>Error: {error}</div>;
}
return <button onClick={handleClick}>Click Me</button>;
}
// For async operations
async function handleSubmit() {
try {
await fetchData();
} catch (error) {
setError(error.message);
}
}
🔹 Error Boundary Best Practices
Follow these guidelines for effective error handling:
// ✅ Good: Wrap at strategic points
<ErrorBoundary>
<Routes />
</ErrorBoundary>
// ✅ Good: Multiple boundaries for isolation
<ErrorBoundary><Sidebar /></ErrorBoundary>
<ErrorBoundary><Content /></ErrorBoundary>
// ✅ Good: Custom fallback UI
<ErrorBoundary fallback={<ErrorPage />}>
// ✅ Good: Log errors to service
componentDidCatch(error, info) {
logToService(error, info);
}
// ❌ Bad: Wrapping every component
<ErrorBoundary><Button /></ErrorBoundary>
// ❌ Bad: No error logging
componentDidCatch() { /* empty */ }
// ❌ Bad: Generic error message
return <div>Error</div>;