React Error Boundaries: a practical guide to resilient component trees
I was using a third-party chart library when it threw an uncaught exception on unusual data. The whole React app unmounted. A blank white screen. Every user on that page saw nothing. If I had placed an error boundary around that chart component, only the chart would have shown an error state — the rest of the page would have kept working. Error boundaries are one of the most underused resilience tools in React.
The reusable ErrorBoundary component
React does not ship a ready-to-use ErrorBoundary class. Install the community package or write your own:
npm install react-error-boundary
import { ErrorBoundary } from 'react-error-boundary';
function FallbackComponent({
error,
resetErrorBoundary,
}: {
error: Error;
resetErrorBoundary: () => void;
}) {
return (
Something went wrong
Error details
{error.message}
);
}
// Wrap any component that might throw
export function DashboardChart({ data }: { data: unknown }) {
return (
);
}
Placement strategy: granular vs coarse
Where you place error boundaries determines how much of your UI survives a failure:
// Coarse: one boundary protects the whole page
// A single widget failure kills the whole page content
export default function DashboardPage() {
return (
);
}
// Granular: each section is isolated
// Recommended for dashboards with independent sections
export default function DashboardPage() {
return (
);
}
Error reporting inside boundaries
import * as Sentry from '@sentry/react';
function ReportingErrorBoundary({
children,
section,
}: {
children: React.ReactNode;
section: string;
}) {
return (
(
This section failed to load
)}
onError={(error, info) => {
// Report to your error tracking service
Sentry.captureException(error, {
extra: {
section,
componentStack: info.componentStack,
},
});
console.error(`Error in ${section}:`, error);
}}
>
{children}
);
}
The reset key pattern
When your data changes (e.g., the user navigates to a different item), you want to reset the error boundary. The resetKeys prop watches values and resets when they change:
function ProductDetail({ productId }: { productId: string }) {
return (
);
}
Async errors are NOT caught by boundaries
Error boundaries only catch synchronous render errors and errors in lifecycle methods. They do NOT catch:
- Async errors in event handlers
- Errors in setTimeout/setInterval callbacks
- Errors in server-side rendering
// This is NOT caught by an error boundary
async function handleClick() {
const data = await fetchData(); // If this throws, it won't hit the boundary
}
// To make async errors catchable, convert to state:
function Component() {
const [error, setError] = useState(null);
if (error) throw error; // Re-throw synchronously so boundary catches it
async function handleClick() {
try {
await fetchData();
} catch (err) {
setError(err as Error); // Triggers re-render, throws synchronously above
}
}
}
Error boundaries are not a way to handle expected errors — use try/catch for those. They are a last-resort safety net for unexpected component failures. Every significant UI section in a production app should have one.