Error Boundaries
An error boundary is a class component that catches render-time errors in its subtree and displays a fallback UI. React deliberately did not ship a hook for this — it relies on componentDidCatch and getDerivedStateFromError. Without one, a render error in any child unmounts the whole React tree, leaving the user staring at a blank page.
A reusable ErrorBoundary with reset and reporting
EXAMPLE
import React from 'react';
// 1) The boundary itself — class component is the only option today
class ErrorBoundary extends React.Component {
state = { error: null };
static getDerivedStateFromError(error) {
return { error };
}
componentDidCatch(error, info) {
// Send to Sentry/Bugsnag/your logger. Never throw from here.
if (this.props.onError) this.props.onError(error, info);
console.error('[ErrorBoundary]', error, info.componentStack);
}
reset = () => this.setState({ error: null });
render() {
if (this.state.error) {
const Fallback = this.props.fallback ?? DefaultFallback;
return <Fallback error={this.state.error} reset={this.reset} />;
}
return this.props.children;
}
}
function DefaultFallback({ error, reset }) {
return (
<div role='alert' style={{ padding: 16, border: '1px solid #f33' }}>
<h2>Something went wrong</h2>
<pre style={{ whiteSpace: 'pre-wrap' }}>{error.message}</pre>
<button onClick={reset}>Try again</button>
</div>
);
}
// 2) Place boundaries at meaningful seams — per-route, per-widget — not at the root
function Dashboard() {
return (
<Layout>
<ErrorBoundary fallback={WidgetFallback}>
<RevenueChart />
</ErrorBoundary>
<ErrorBoundary fallback={WidgetFallback}>
<ActivityFeed />
</ErrorBoundary>
</Layout>
);
}
function WidgetFallback({ error, reset }) {
return (
<div className='widget-fallback'>
<p>This widget crashed.</p>
<button onClick={reset}>Reload widget</button>
</div>
);
}
// 3) What boundaries DO NOT catch:
// - Event handlers (use try/catch inside the handler)
// - Async code, promises (use .catch and surface via state)
// - Server-side rendering errors (handle on the server)
// - Errors in the boundary itself
// 4) Reporting hook used at the top of the tree
function reportToSentry(error, info) {
if (typeof window !== 'undefined' && window.Sentry) {
window.Sentry.captureException(error, { extra: { componentStack: info.componentStack } });
}
}
export default function App() {
return (
<ErrorBoundary onError={reportToSentry}>
<Dashboard />
</ErrorBoundary>
);
}
Why it matters
Place boundaries at boundaries — per route, per widget, per third-party embed — not just at the root. A single top-level boundary still wipes the screen on any crash; nested boundaries let the surviving parts of the page keep working while the broken one shows a recoverable fallback.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
class Boundary extends Component {
state = { error: null };
static getDerivedStateFromError(error) { return { error }; }
render() { return this.state.error ? <Fallback /> : this.props.children; }
}
Try it Yourself »
Discussion
Loading…