| name | react-errors-boundaries |
| description | Use when building React components that need error recovery and graceful failure handling. Prevents the common mistake of letting uncaught errors crash the entire application tree. Covers Error Boundary class components, getDerivedStateFromError, componentDidCatch, recovery patterns, nested boundaries, placement strategy, error boundary limitations. Keywords: ErrorBoundary, getDerivedStateFromError, componentDidCatch, fallback UI, app crashes, white screen of death, catch error in component, fallback UI, error recovery..
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires React 18.x or 19.x with TypeScript. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
react-errors-boundaries
Quick Reference
Error Boundary Overview
| Aspect | Detail |
|---|
| Component type | Class component ONLY (no hook equivalent exists) |
| Catches | Errors during rendering, lifecycle methods, constructors of child tree |
| Does NOT catch | Event handlers, async code, SSR errors, errors in the boundary itself |
| React 18 | getDerivedStateFromError + componentDidCatch |
| React 19 | Same API + onCaughtError/onUncaughtError callbacks on createRoot |
| Recommended library | react-error-boundary (function component wrapper with hooks) |
Lifecycle Methods
| Method | Phase | Purpose | Side Effects? |
|---|
static getDerivedStateFromError(error) | Render | Return state update to show fallback UI | NO — must be pure |
componentDidCatch(error, info) | Commit | Log errors to reporting service | YES — side effects allowed |
Critical Warnings
NEVER use a function component as an error boundary — React has NO hook equivalent for getDerivedStateFromError. Error boundaries MUST be class components or use the react-error-boundary library.
NEVER rely on error boundaries to catch event handler errors — ALWAYS use try/catch inside event handlers. Error boundaries only catch errors during rendering and lifecycle methods.
NEVER rely on error boundaries to catch async errors (promises, setTimeout) — ALWAYS use .catch() or try/catch in async code. The one exception: errors thrown inside startTransition callbacks ARE caught by error boundaries.
NEVER expect an error boundary to catch its own errors — if the boundary itself throws, a PARENT boundary must catch it. ALWAYS have a top-level boundary as a safety net.
NEVER use error boundaries as a substitute for proper input validation — error boundaries are a safety net for unexpected failures, not a control flow mechanism.
ALWAYS implement both getDerivedStateFromError AND componentDidCatch — the first renders fallback UI, the second logs the error. Using only one gives an incomplete error boundary.
ALWAYS provide a way to recover from errors — a "Try Again" button, navigation link, or key-based reset. A dead-end error screen frustrates users.
Complete TypeScript Error Boundary
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface ErrorBoundaryProps {
children: ReactNode;
fallback: ReactNode | ((error: Error, reset: () => void) => ReactNode);
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(: ): <> {
{ : , error };
}
(: , : ): {
.(, error);
.(, info.);
..?.(error, info);
}
handleReset = (): {
.({ : , : });
};
(): {
(.. && ..) {
( .. === ) {
..(.., .);
}
..;
}
..;
}
}
;
Usage:
<ErrorBoundary fallback={<p>Something went wrong.</p>}>
<UserProfile userId={userId} />
</ErrorBoundary>
<ErrorBoundary
fallback={(error, reset) => (
<div role="alert">
<p>Error: {error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
)}
onError={(error, info) => logToService(error, info)}
>
<Dashboard />
</ErrorBoundary>
What Error Boundaries DO NOT Catch
| Scenario | Why Not Caught | Use Instead |
|---|
| Event handlers | Run outside React render cycle | try/catch in the handler |
Async code (setTimeout, promises) | Executes after render completes | .catch() or try/catch |
| Server-side rendering (SSR) | Different execution context | Server-side error handling |
| Errors in the boundary itself | Cannot catch its own errors | A parent error boundary |
Exception: Errors thrown inside startTransition callbacks ARE caught by error boundaries.
Boundary Placement Strategy
Decision Tree
Is this the app root?
├── YES → Page-level boundary (catch-all safety net)
│ Shows generic "Something went wrong" with reload option
│
└── NO → Is this an independent feature/widget?
├── YES → Feature-level boundary
│ Isolates feature failure from rest of page
│
└── NO → Is this a single component that might fail?
├── YES → Component-level boundary
│ Granular recovery, minimal UI disruption
│
└── NO → No boundary needed here
Placement Levels
<ErrorBoundary fallback={<FullPageError />}>
<App />
</ErrorBoundary>
<ErrorBoundary fallback={<SidebarError />}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<FeedError />}>
<NewsFeed />
</ErrorBoundary>
<ErrorBoundary fallback={<ChartPlaceholder />}>
<RevenueChart data={data} />
</ErrorBoundary>
ALWAYS have at minimum a page-level error boundary wrapping the entire application.
NEVER wrap every single component in a boundary — this creates excessive overhead and fragments the UI. Use boundaries at natural isolation points.
Error Recovery Patterns
Pattern 1: Key Prop Reset
Force React to unmount and remount the component by changing the key:
function RecoverableWidget({ userId }: { userId: string }) {
const [errorKey, setErrorKey] = useState<number>(0);
return (
<ErrorBoundary
key={errorKey}
fallback={
<button onClick={() => setErrorKey((k) => k + 1)}>
Retry
</button>
}
>
<UserWidget userId={userId} />
</ErrorBoundary>
);
}
Pattern 2: Reset Callback
Use the boundary's internal reset method (as shown in the complete implementation above):
<ErrorBoundary
fallback={(error, reset) => (
<div role="alert">
<p>Failed to load: {error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
)}
>
<DataTable />
</ErrorBoundary>
Pattern 3: Navigate Away
Redirect to a safe route on error:
function NavigatingFallback({ error }: { error: Error }) {
return (
<div role="alert">
<p>This page encountered an error.</p>
<a href="/">Return to Home</a>
</div>
);
}
Nested Boundaries
Inner boundaries catch errors first. If the inner boundary itself fails, the outer boundary catches it.
<ErrorBoundary fallback={<AppCrashScreen />}> {}
<Header />
<ErrorBoundary fallback={<ContentError />}> {/* Inner: feature-level */}
<ErrorBoundary fallback={<WidgetError />}> {/* Innermost: component */}
<ComplexWidget />
</ErrorBoundary>
<OtherContent />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
Behavior: If ComplexWidget throws, the innermost boundary shows <WidgetError />. The rest of the page (Header, OtherContent, Footer) remains functional.
react-error-boundary Library
The react-error-boundary package provides a function-component-based API. ALWAYS use this library instead of writing custom class components.
npm install react-error-boundary
ErrorBoundary Component
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>
);
}
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, info) => logToService(error, info)}
onReset={(details) => {
}}
resetKeys={[userId]}
>
<UserProfile userId={userId} />
</ErrorBoundary>
useErrorBoundary Hook
Trigger the nearest error boundary from event handlers or async code:
import { useErrorBoundary } from 'react-error-boundary';
function UserActions() {
const { showBoundary } = useErrorBoundary();
async function handleDelete() {
try {
await deleteUser();
} catch (error) {
showBoundary(error);
}
}
return <button onClick={handleDelete}>Delete Account</button>;
}
This solves the limitation that error boundaries cannot catch event handler or async errors.
React 19: Root-Level Error Callbacks
React 19 adds error reporting callbacks on createRoot:
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!, {
onCaughtError: (error, errorInfo) => {
console.error('Caught by boundary:', error, errorInfo.componentStack);
},
onUncaughtError: (error, errorInfo) => {
console.error('Uncaught:', error, errorInfo.componentStack);
},
onRecoverableError: (error, errorInfo) => {
console.error('Recovered:', error, errorInfo.componentStack);
},
});
These callbacks complement error boundaries — they do NOT replace them.
Testing Error Boundaries
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
function ThrowingComponent({ shouldThrow }: { shouldThrow: boolean }) {
if (shouldThrow) {
throw new Error('Test error');
}
return <p>Content is fine</p>;
}
describe('ErrorBoundary', () => {
const originalError = console.error;
beforeAll(() => { console.error = vi.fn(); });
afterAll(() => { console.error = originalError; });
it('renders children when no error', () => {
render(
<ErrorBoundary fallback={<p>Error occurred</p>}>
<ThrowingComponent = />
);
(screen.()).();
});
(, {
(
);
(screen.()).();
});
(, {
onError = vi.();
(
);
(onError).(
expect.({ : }),
expect.({ : expect.() })
);
});
});
Development vs Production Behavior
| Behavior | Development | Production |
|---|
| Error bubbling | Errors bubble to window.onerror even when caught | Only caught by error boundaries |
| Console output | React logs full component stack | Minimal logging |
| Strict Mode | Components render twice to detect impurities | Single render |
Reference Links
Official Sources