Error Boundaries
Error boundary pattern for gracefully catching and recovering from runtime errors in React component trees
You are an expert in the Error Boundary pattern for building React applications. ## Key Points - **Error Catching Scope**: An error boundary catches errors thrown during render and in lifecycle methods of its entire subtree. - **Fallback UI**: When an error is caught, the boundary renders a fallback instead of the broken tree. - **Recovery**: Error boundaries can offer retry mechanisms that reset their internal error state and attempt to re-render the children. - **Limitations**: Error boundaries do not catch errors in event handlers, asynchronous code (setTimeout, fetch), or server-side rendering. Those require try-catch or `.catch()`. - Place error boundaries at meaningful UI boundaries — page level, widget level, or feature section level. - Always pair Suspense boundaries with an outer Error Boundary to handle rejected promises. - Log caught errors to a monitoring service (Sentry, Datadog, etc.) in `componentDidCatch` or `onError`. - Provide actionable fallback UIs with retry buttons, not just "something went wrong." - Use `resetKeys` in `react-error-boundary` to auto-reset the boundary when relevant props change (e.g., after navigation). - **One giant boundary at the root**: A single top-level boundary replaces the entire UI on any error. Use multiple granular boundaries so only the broken section fails. - **Catching event handler errors**: Error boundaries do not catch errors in `onClick`, `onChange`, etc. Use local try-catch in event handlers. - **Not resetting state**: After an error, re-rendering the same broken component without resetting its state will throw again. Provide a reset mechanism.
skilldb get react-patterns-skills/Error BoundariesFull skill: 198 linesError Boundaries — React Patterns
You are an expert in the Error Boundary pattern for building React applications.
Overview
Error boundaries are React components that catch JavaScript errors in their child component tree during rendering, in lifecycle methods, and in constructors. They prevent a single broken component from crashing the entire application by rendering a fallback UI instead. Error boundaries are class components that implement componentDidCatch and/or static getDerivedStateFromError. In modern React, they are often wrapped in a reusable library component like react-error-boundary.
Core Philosophy
Error boundaries embody a fundamental principle of resilient systems: failures should be isolated, not propagated. A single broken widget on a dashboard should not bring down the navigation, the sidebar, and every other widget on the page. Without error boundaries, an unhandled throw during render crashes the entire React tree, leaving users staring at a blank screen. Error boundaries draw fault lines through your UI so that failures are contained to the smallest meaningful section.
The philosophy extends beyond crash prevention to user experience design. A well-placed error boundary does not just catch errors — it communicates what went wrong and offers a path forward. A retry button, a link to refresh, or a message explaining which section failed gives users agency. Silent failures and generic "something went wrong" messages are almost as bad as a full crash because they leave users without recourse.
Thinking in error boundaries also forces you to think about your component tree's architecture. Where you place boundaries reveals which parts of your UI are independent and which are tightly coupled. If you cannot place a boundary around a section without breaking the page, that section is too entangled with its surroundings. Error boundaries are both a safety mechanism and an architectural litmus test.
Core Concepts
- Error Catching Scope: An error boundary catches errors thrown during render and in lifecycle methods of its entire subtree.
- Fallback UI: When an error is caught, the boundary renders a fallback instead of the broken tree.
- Recovery: Error boundaries can offer retry mechanisms that reset their internal error state and attempt to re-render the children.
- Limitations: Error boundaries do not catch errors in event handlers, asynchronous code (setTimeout, fetch), or server-side rendering. Those require try-catch or
.catch().
Implementation Patterns
Class-Based Error Boundary
import { Component, ErrorInfo, ReactNode } from "react";
interface Props {
fallback: ReactNode | ((error: Error, reset: () => void) => ReactNode);
onError?: (error: Error, info: ErrorInfo) => void;
children: ReactNode;
}
interface State {
error: Error | null;
}
class ErrorBoundary extends Component<Props, State> {
state: State = { error: null };
static getDerivedStateFromError(error: Error): State {
return { error };
}
componentDidCatch(error: Error, info: ErrorInfo) {
this.props.onError?.(error, info);
}
reset = () => {
this.setState({ error: null });
};
render() {
if (this.state.error) {
const { fallback } = this.props;
if (typeof fallback === "function") {
return fallback(this.state.error, this.reset);
}
return fallback;
}
return this.props.children;
}
}
export { ErrorBoundary };
Usage with Fallback UI
<ErrorBoundary
fallback={(error, reset) => (
<div role="alert">
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
<button onClick={reset}>Try again</button>
</div>
)}
onError={(error, info) => {
// Report to monitoring service
reportError(error, info.componentStack);
}}
>
<Dashboard />
</ErrorBoundary>
Using react-error-boundary Library
import { ErrorBoundary } from "react-error-boundary";
function ErrorFallback({ error, resetErrorBoundary }: {
error: Error;
resetErrorBoundary: () => void;
}) {
return (
<div role="alert">
<p>Something went wrong:</p>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => {
// Clear cached state, refetch, etc.
}}
resetKeys={[userId]}
>
<UserProfile userId={userId} />
</ErrorBoundary>
);
}
Granular Error Boundaries
function DashboardPage() {
return (
<div className="grid grid-cols-3 gap-4">
<ErrorBoundary fallback={<WidgetError name="Revenue" />}>
<RevenueWidget />
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Users" />}>
<UsersWidget />
</ErrorBoundary>
<ErrorBoundary fallback={<WidgetError name="Orders" />}>
<OrdersWidget />
</ErrorBoundary>
</div>
);
}
function WidgetError({ name }: { name: string }) {
return (
<div className="p-4 bg-red-50 rounded">
<p>{name} widget failed to load.</p>
</div>
);
}
Combining with Suspense
function DataSection() {
return (
<ErrorBoundary fallback={<ErrorMessage />}>
<Suspense fallback={<Skeleton />}>
<AsyncDataComponent />
</Suspense>
</ErrorBoundary>
);
}
Best Practices
- Place error boundaries at meaningful UI boundaries — page level, widget level, or feature section level.
- Always pair Suspense boundaries with an outer Error Boundary to handle rejected promises.
- Log caught errors to a monitoring service (Sentry, Datadog, etc.) in
componentDidCatchoronError. - Provide actionable fallback UIs with retry buttons, not just "something went wrong."
- Use
resetKeysinreact-error-boundaryto auto-reset the boundary when relevant props change (e.g., after navigation).
Common Pitfalls
- One giant boundary at the root: A single top-level boundary replaces the entire UI on any error. Use multiple granular boundaries so only the broken section fails.
- Catching event handler errors: Error boundaries do not catch errors in
onClick,onChange, etc. Use local try-catch in event handlers. - Not resetting state: After an error, re-rendering the same broken component without resetting its state will throw again. Provide a reset mechanism.
- Silent failures: Catching errors without logging or reporting makes debugging impossible.
- Forgetting async errors: Errors in
useEffectcallbacks, promises, and setTimeout are not caught by error boundaries. Handle these with.catch()and local error state.
Anti-Patterns
-
The single root boundary: Wrapping only the app root in one error boundary. Any error in any component replaces the entire UI with a fallback, which is functionally equivalent to a crash. Granular boundaries at the page, section, or widget level preserve the rest of the application.
-
Catch-and-ignore: Implementing
componentDidCatchoronErrorwithout logging, reporting, or surfacing the error. This swallows failures silently, making production bugs invisible and impossible to diagnose. -
Retry without state reset: Offering a "Try again" button that re-renders the same broken component without clearing the state that caused the error. The component throws again immediately, creating an infinite error-reset loop.
-
Using error boundaries for expected states: Relying on error boundaries to handle empty data, loading failures from
fetch, or validation errors. Error boundaries are for unexpected runtime crashes. Expected failure modes should be handled with conditional rendering, error state, and user-facing messages in the normal render path. -
Duplicating boundary logic across the codebase: Writing a new class-based error boundary component every time one is needed instead of using a reusable boundary component or a library like
react-error-boundary. This leads to inconsistent error handling, duplicated logging logic, and missed edge cases.
Install this skill directly: skilldb add react-patterns-skills
Related Skills
Compound Components
Compound component pattern for building flexible, implicitly-shared React component APIs
Context Patterns
React Context patterns for efficient state sharing, provider composition, and avoiding unnecessary re-renders
Custom Hooks
Custom hooks pattern for extracting and reusing stateful logic across React components
Optimistic Updates
Optimistic update patterns for instant UI feedback with server reconciliation and rollback in React
Render Props
Render props pattern for sharing cross-cutting logic through function-as-children and render callbacks
Server Components
React Server Components pattern for zero-bundle server-rendered components with direct backend access