Error Boundaries in Production
Contain rendering failures to a subtree, report them, and give users a way back — not a white screen.
An error boundary catches errors thrown while rendering its child tree, shows a fallback instead of unmounting your whole app, and gives you a hook to log the failure. In production this is the difference between one broken widget and a blank white page.
What they catch — and what they do not
- Caught: errors during rendering, in lifecycle methods, and in constructors of the subtree below.
- Not caught: errors in event handlers, in async code (
setTimeout, promises), during SSR, and errors thrown in the boundary itself. Handle those with ordinarytry/catchand by setting error state.
Still the one place classes remain
Error boundaries require getDerivedStateFromError (to render a fallback) and/or componentDidCatch (to log), which have no hook equivalent yet. Most teams write one small class — or use react-error-boundary for a friendly function API with a built-in reset.
<ErrorBoundary fallback={<p>Something broke.</p>}>
<Widget />
</ErrorBoundary>Design your boundaries deliberately
- Granular, not global. Wrap each risky region — a chart, a third-party embed, a route — so one failure does not blank its neighbors.
- Offer recovery. A Try again button that resets the boundary (often keyed on the current route or resource) beats forcing a full reload.
- Report it. In
componentDidCatch, send the error and component stack to your monitoring so you learn about failures users never report.
Pair boundaries with Suspense: Suspense owns loading, the boundary owns failed. Together they cover every outcome of async UI.
Example
import { Component } from 'react';
class ErrorBoundary extends Component {
state = { error: null };
static getDerivedStateFromError(error) {
return { error }; // render the fallback on next paint
}
componentDidCatch(error, info) {
// Send to monitoring (Sentry, Datadog, your API...).
reportError({ error, componentStack: info.componentStack });
}
reset = () => this.setState({ error: null });
render() {
if (this.state.error) {
return (
<div role="alert">
<p>This section failed to load.</p>
<button onClick={this.reset}>Try again</button>
</div>
);
}
return this.props.children;
}
}
function reportError(payload) {
console.error('Reported:', payload);
}
function Dashboard() {
return (
<ErrorBoundary>
<RiskyChart />
</ErrorBoundary>
);
}When to use it
- Wrapping each dashboard widget in its own error boundary so a broken chart shows a 'Widget failed to load' fallback without taking down the rest of the dashboard.
- A top-level error boundary that catches unexpected rendering failures, shows a friendly error page, and posts the error with component stack to Sentry.
- Resetting an error boundary via a route-change key so users who navigate away from a broken page get a clean component instance on their next visit.
More examples
Minimal class error boundary
getDerivedStateFromError triggers the fallback UI and componentDidCatch fires side-effects like error logging, keeping rendering and reporting separate.
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error, info) {
reportError(error, info.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback ?? <p>Something went wrong.</p>;
}
return this.props.children;
}
}Per-widget boundary with retry
Each widget gets its own boundary so one broken component shows a retry button while the rest of the dashboard continues to work.
function WidgetShell({ id, children }) {
return (
<ErrorBoundary
key={id}
fallback={
<div className="widget-error">
<p>This widget failed to load.</p>
<button onClick={() => window.location.reload()}>Retry</button>
</div>
}
>
{children}
</ErrorBoundary>
);
}
<WidgetShell id="revenue-chart"><RevenueChart /></WidgetShell>
<WidgetShell id="user-table"><UserTable /></WidgetShell>Auto-reset boundary on navigation
Binding the boundary's key to the pathname forces React to remount it on navigation, automatically clearing any previous error state without manual reset logic.
import { useLocation } from 'react-router-dom';
function App() {
const location = useLocation();
// key changes on every route change, discarding any error state
return (
<ErrorBoundary key={location.pathname}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</ErrorBoundary>
);
}
Discussion