
React Errors Boundaries
- 12 installs
- 6 repo stars
- Updated July 8, 2026
- openaec-foundation/react-claude-skill-package
Helps with frontend development tasks.
About
react-errors-boundaries is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted development.
- react-errors-boundaries
- Frontend Development
- AI-coding skill
React Errors Boundaries by the numbers
- 12 all-time installs (skills.sh)
- Ranked #1,643 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/openaec-foundation/react-claude-skill-package --skill react-errors-boundariesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 12 |
|---|---|
| repo stars | ★ 6 |
| Last updated | July 8, 2026 |
| Repository | openaec-foundation/react-claude-skill-package ↗ |
What it does
Helps with frontend development tasks.
Files
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): Partial<ErrorBoundaryState> {
// Render phase — NO side effects. Return state for fallback UI.
return { hasError: true, error };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
// Commit phase — side effects allowed. Log to error service.
console.error('Error boundary caught:', error);
console.error('Component stack:', info.componentStack);
this.props.onError?.(error, info);
}
private handleReset = (): void => {
this.setState({ hasError: false, error: null });
};
render(): ReactNode {
if (this.state.hasError && this.state.error) {
if (typeof this.props.fallback === 'function') {
return this.props.fallback(this.state.error, this.handleReset);
}
return this.props.fallback;
}
return this.props.children;
}
}
export default ErrorBoundary;Usage:
// Static fallback
<ErrorBoundary fallback={<p>Something went wrong.</p>}>
<UserProfile userId={userId} />
</ErrorBoundary>
// Dynamic fallback with reset
<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 herePlacement Levels
// Level 1: Page-level (root safety net) — ALWAYS have this
<ErrorBoundary fallback={<FullPageError />}>
<App />
</ErrorBoundary>
// Level 2: Feature-level (isolate independent sections)
<ErrorBoundary fallback={<SidebarError />}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<FeedError />}>
<NewsFeed />
</ErrorBoundary>
// Level 3: Component-level (granular recovery)
<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 />}> {/* Outer: last resort */}
<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-boundaryErrorBoundary 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) => {
// Reset app state that caused the error
}}
resetKeys={[userId]} // Auto-reset when these values change
>
<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); // Triggers nearest ErrorBoundary
}
}
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) => {
// Errors caught by an Error Boundary
console.error('Caught by boundary:', error, errorInfo.componentStack);
},
onUncaughtError: (error, errorInfo) => {
// Errors NOT caught by any Error Boundary
console.error('Uncaught:', error, errorInfo.componentStack);
},
onRecoverableError: (error, errorInfo) => {
// Errors React recovers from automatically
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';
// Component that throws on demand
function ThrowingComponent({ shouldThrow }: { shouldThrow: boolean }) {
if (shouldThrow) {
throw new Error('Test error');
}
return <p>Content is fine</p>;
}
describe('ErrorBoundary', () => {
// Suppress console.error noise in test output
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 shouldThrow={false} />
</ErrorBoundary>
);
expect(screen.getByText('Content is fine')).toBeInTheDocument();
});
it('renders fallback when child throws', () => {
render(
<ErrorBoundary fallback={<p>Error occurred</p>}>
<ThrowingComponent shouldThrow={true} />
</ErrorBoundary>
);
expect(screen.getByText('Error occurred')).toBeInTheDocument();
});
it('calls onError callback with error details', () => {
const onError = vi.fn();
render(
<ErrorBoundary fallback={<p>Error</p>} onError={onError}>
<ThrowingComponent shouldThrow={true} />
</ErrorBoundary>
);
expect(onError).toHaveBeenCalledWith(
expect.objectContaining({ message: 'Test error' }),
expect.objectContaining({ componentStack: expect.any(String) })
);
});
});---
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
- references/examples.md -- Error boundary patterns and real-world implementations
- references/anti-patterns.md -- Common error boundary mistakes and how to avoid them
Official Sources
- https://react.dev/reference/react/Component (getDerivedStateFromError, componentDidCatch)
- https://react.dev/reference/react-dom/client/createRoot (onCaughtError, onUncaughtError)
- https://github.com/bvaughn/react-error-boundary (react-error-boundary library)
Error Boundary Anti-Patterns
Anti-Pattern 1: Using a Function Component as an Error Boundary
// WRONG: Function components cannot be error boundaries
function ErrorBoundary({ children }: { children: React.ReactNode }) {
const [hasError, setHasError] = useState(false);
// This does NOT work — there is no hook equivalent for
// getDerivedStateFromError or componentDidCatch
useEffect(() => {
// Cannot catch render errors here
}, []);
if (hasError) return <p>Error</p>;
return <>{children}</>;
}WHY this is wrong: React has NO hook equivalent for catching render-phase errors. The getDerivedStateFromError and componentDidCatch lifecycle methods are exclusive to class components. ALWAYS use a class component or the react-error-boundary library.
---
Anti-Pattern 2: Catching Event Handler Errors with Boundaries
// WRONG: Error boundaries do NOT catch event handler errors
<ErrorBoundary fallback={<p>Error</p>}>
<button onClick={() => {
throw new Error('Click failed'); // NOT caught by boundary
}}>
Click Me
</button>
</ErrorBoundary>WHY this is wrong: Event handlers execute outside the React render cycle. Error boundaries only intercept errors during rendering, lifecycle methods, and constructors.
CORRECT approach:
function SafeButton() {
const handleClick = () => {
try {
riskyOperation();
} catch (error) {
// Handle error locally or use useErrorBoundary from react-error-boundary
console.error('Click failed:', error);
}
};
return <button onClick={handleClick}>Click Me</button>;
}
// Or with react-error-boundary's useErrorBoundary hook:
function SafeButton() {
const { showBoundary } = useErrorBoundary();
const handleClick = () => {
try {
riskyOperation();
} catch (error) {
showBoundary(error); // Now the boundary WILL catch it
}
};
return <button onClick={handleClick}>Click Me</button>;
}---
Anti-Pattern 3: Missing getDerivedStateFromError
// WRONG: Only componentDidCatch, no fallback UI
class IncompleteErrorBoundary extends Component<Props, State> {
state = { hasError: false };
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error(error);
this.setState({ hasError: true }); // Setting state here is unreliable
}
render() {
if (this.state.hasError) return this.props.fallback;
return this.props.children;
}
}WHY this is wrong: componentDidCatch runs in the commit phase, AFTER rendering. Without getDerivedStateFromError, React attempts to render the broken child tree first, which can cause additional errors or a brief flash of broken UI. ALWAYS use getDerivedStateFromError to set the error state — it runs during the render phase and prevents the broken tree from ever being committed.
---
Anti-Pattern 4: Swallowing Errors Without Logging
// WRONG: Error is caught but never reported
class SilentBoundary extends Component<Props, State> {
state = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
// Missing componentDidCatch — error is silently swallowed
// No logging, no reporting, no way to diagnose issues
render() {
if (this.state.hasError) return <p>Something went wrong.</p>;
return this.props.children;
}
}WHY this is wrong: Without componentDidCatch or an onError callback, errors disappear into a black hole. You have no visibility into production failures. ALWAYS log errors to a reporting service in componentDidCatch.
---
Anti-Pattern 5: Wrapping Every Component in a Boundary
// WRONG: Excessive granularity
function App() {
return (
<ErrorBoundary fallback={<p>Error</p>}>
<ErrorBoundary fallback={<p>Error</p>}>
<Header />
</ErrorBoundary>
<ErrorBoundary fallback={<p>Error</p>}>
<Nav />
</ErrorBoundary>
<ErrorBoundary fallback={<p>Error</p>}>
<ErrorBoundary fallback={<p>Error</p>}>
<Title />
</ErrorBoundary>
<ErrorBoundary fallback={<p>Error</p>}>
<Subtitle />
</ErrorBoundary>
</ErrorBoundary>
</ErrorBoundary>
);
}WHY this is wrong: Excessive boundaries add React tree overhead, make the component tree unreadable, and fragment the fallback UI into tiny pieces that confuse users. Place boundaries at natural isolation points: app root, feature sections, and components known to be error-prone.
---
Anti-Pattern 6: Error Boundary Without Recovery Path
// WRONG: Dead-end error screen with no way out
class DeadEndBoundary extends Component<Props, State> {
state = { hasError: false };
static getDerivedStateFromError(): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error(error);
}
render() {
if (this.state.hasError) {
return <p>Something went wrong.</p>; // No button, no link, no escape
}
return this.props.children;
}
}WHY this is wrong: The user is stuck on a dead-end screen. ALWAYS provide at least one recovery option: a "Try Again" button, a link to the home page, or a page refresh button.
CORRECT approach:
render() {
if (this.state.hasError) {
return (
<div role="alert">
<p>Something went wrong.</p>
<button onClick={() => this.setState({ hasError: false, error: null })}>
Try Again
</button>
<a href="/">Return to Home</a>
</div>
);
}
return this.props.children;
}---
Anti-Pattern 7: Relying on Error Boundaries for Async Errors
// WRONG: Async errors bypass error boundaries
function AsyncComponent() {
useEffect(() => {
// This error is NOT caught by any error boundary
fetch('/api/data')
.then((res) => res.json())
.then((data) => {
throw new Error('Processing failed'); // NOT caught
});
}, []);
return <p>Loading...</p>;
}WHY this is wrong: Promise rejections and errors in async code execute outside the React render cycle. Error boundaries cannot intercept them.
CORRECT approach:
function AsyncComponent() {
const { showBoundary } = useErrorBoundary();
useEffect(() => {
let cancelled = false;
fetch('/api/data')
.then((res) => res.json())
.then((data) => {
if (!cancelled) processData(data);
})
.catch((error) => {
if (!cancelled) showBoundary(error); // Bridge to error boundary
});
return () => { cancelled = true; };
}, [showBoundary]);
return <p>Loading...</p>;
}---
Anti-Pattern 8: Throwing in getDerivedStateFromError
// WRONG: Side effects in getDerivedStateFromError
class BadBoundary extends Component<Props, State> {
static getDerivedStateFromError(error: Error): State {
// WRONG: This runs during the render phase — no side effects!
fetch('/api/log-error', {
method: 'POST',
body: JSON.stringify({ error: error.message }),
});
return { hasError: true };
}
}WHY this is wrong: getDerivedStateFromError runs during the render phase, which must be pure with no side effects. Network requests, DOM manipulation, and other side effects MUST go in componentDidCatch (commit phase).
---
Anti-Pattern 9: Using Error Boundaries for Control Flow
// WRONG: Using errors as a data-passing mechanism
function DataComponent({ data }: { data: unknown }) {
if (!data) {
throw new Error('NO_DATA'); // Abuse of error boundaries
}
return <p>{JSON.stringify(data)}</p>;
}
// Parent checks error type to decide what to show
class ControlFlowBoundary extends Component<Props, State> {
static getDerivedStateFromError(error: Error): State {
if (error.message === 'NO_DATA') {
return { hasError: true, showEmpty: true };
}
return { hasError: true, showEmpty: false };
}
}WHY this is wrong: Error boundaries are for unexpected failures, not expected application states. Use conditional rendering for expected states (loading, empty, error). Use error boundaries only as a safety net for genuinely unexpected exceptions.
CORRECT approach:
function DataComponent({ data }: { data: unknown }) {
if (!data) return <EmptyState />;
return <p>{JSON.stringify(data)}</p>;
}---
Summary: Error Boundary Rules
| Rule | Rationale |
|---|---|
ALWAYS use a class component or react-error-boundary | No hook equivalent exists |
ALWAYS implement both getDerivedStateFromError AND componentDidCatch | Fallback UI + error logging |
| ALWAYS provide a recovery path in fallback UI | Dead-end screens frustrate users |
ALWAYS log errors in componentDidCatch, not getDerivedStateFromError | Render phase must be pure |
| NEVER rely on boundaries for event handler errors | Use try/catch or useErrorBoundary |
| NEVER rely on boundaries for async errors | Use .catch() or useErrorBoundary |
| NEVER wrap every component in a boundary | Place at natural isolation points |
| NEVER use error boundaries for control flow | Use conditional rendering instead |
Error Boundary Patterns — Examples
Pattern 1: Minimal Reusable Error Boundary
The simplest production-ready boundary with TypeScript types:
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback: ReactNode;
}
interface State {
hasError: boolean;
}
class ErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(_error: Error): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
console.error('ErrorBoundary caught:', error, info.componentStack);
}
render(): ReactNode {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}---
Pattern 2: Error Boundary with Reset Capability
Allows users to recover by retrying the failed operation:
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback: (error: Error, reset: () => void) => ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}
interface State {
hasError: boolean;
error: Error | null;
}
class ResettableErrorBoundary extends Component<Props, State> {
state: State = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): Partial<State> {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
this.props.onError?.(error, info);
}
private reset = (): void => {
this.setState({ hasError: false, error: null });
};
render(): ReactNode {
if (this.state.hasError && this.state.error) {
return this.props.fallback(this.state.error, this.reset);
}
return this.props.children;
}
}
// Usage
<ResettableErrorBoundary
fallback={(error, reset) => (
<div role="alert">
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button onClick={reset}>Try Again</button>
</div>
)}
onError={(error, info) => {
errorReportingService.log(error, info.componentStack);
}}
>
<Dashboard />
</ResettableErrorBoundary>---
Pattern 3: Key-Based Recovery
Force full remount by changing the boundary's key:
import { useState } from 'react';
function RecoverableSection() {
const [boundaryKey, setBoundaryKey] = useState<number>(0);
return (
<ErrorBoundary
key={boundaryKey}
fallback={
<div role="alert">
<p>This section failed to load.</p>
<button onClick={() => setBoundaryKey((k) => k + 1)}>
Reload Section
</button>
</div>
}
>
<ComplexFeature />
</ErrorBoundary>
);
}When to use: When the error is caused by corrupted component state. Changing the key forces React to destroy the entire subtree and create it fresh.
---
Pattern 4: Multi-Level Boundary Strategy
A real application with boundaries at three levels:
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
// Level 1: App-wide safety net
<ErrorBoundary
FallbackComponent={FullPageError}
onError={(error, info) => logCritical(error, info)}
>
<Header />
<main>
{/* Level 2: Feature isolation */}
<ErrorBoundary
FallbackComponent={SidebarError}
resetKeys={[currentRoute]}
>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary
FallbackComponent={ContentError}
resetKeys={[currentRoute]}
>
{/* Level 3: Component-level */}
<ErrorBoundary fallbackRender={({ error }) => (
<ChartPlaceholder message={error.message} />
)}>
<RevenueChart />
</ErrorBoundary>
<ErrorBoundary fallbackRender={({ error }) => (
<TablePlaceholder message={error.message} />
)}>
<DataTable />
</ErrorBoundary>
</ErrorBoundary>
</main>
<Footer />
</ErrorBoundary>
);
}
function FullPageError({ error, resetErrorBoundary }: {
error: Error;
resetErrorBoundary: () => void;
}) {
return (
<div role="alert" style={{ padding: '2rem', textAlign: 'center' }}>
<h1>Application Error</h1>
<p>An unexpected error occurred. Please try refreshing the page.</p>
<button onClick={resetErrorBoundary}>Refresh</button>
</div>
);
}
function SidebarError({ resetErrorBoundary }: {
error: Error;
resetErrorBoundary: () => void;
}) {
return (
<aside>
<p>Sidebar unavailable</p>
<button onClick={resetErrorBoundary}>Retry</button>
</aside>
);
}---
Pattern 5: react-error-boundary with resetKeys
Auto-reset the boundary when specific values change (e.g., route navigation):
import { ErrorBoundary } from 'react-error-boundary';
import { useLocation } from 'react-router-dom';
function PageContent() {
const location = useLocation();
return (
<ErrorBoundary
FallbackComponent={PageError}
resetKeys={[location.pathname]}
onReset={(details) => {
// Optionally clear any cached state
queryClient.clear();
}}
>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</ErrorBoundary>
);
}Behavior: When the user navigates to a different route, resetKeys changes, and the error boundary automatically clears its error state. No manual "Try Again" needed.
---
Pattern 6: useErrorBoundary for Async/Event Errors
Bridge event handler and async errors into the error boundary system:
import { useErrorBoundary } from 'react-error-boundary';
function DataLoader({ endpoint }: { endpoint: string }) {
const { showBoundary } = useErrorBoundary();
const [data, setData] = useState<unknown>(null);
useEffect(() => {
let cancelled = false;
fetch(endpoint)
.then((res) => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then((json) => {
if (!cancelled) setData(json);
})
.catch((error) => {
if (!cancelled) showBoundary(error);
});
return () => { cancelled = true; };
}, [endpoint, showBoundary]);
if (!data) return <p>Loading...</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
// Wrap in ErrorBoundary
<ErrorBoundary FallbackComponent={DataError}>
<DataLoader endpoint="/api/users" />
</ErrorBoundary>---
Pattern 7: Error Boundary with Error Reporting Service
Production pattern with structured error logging:
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface ErrorReport {
error: {
name: string;
message: string;
stack?: string;
};
componentStack: string;
timestamp: string;
userAgent: string;
url: string;
}
interface Props {
children: ReactNode;
fallback: ReactNode;
serviceName: string;
}
interface State {
hasError: boolean;
}
class ReportingErrorBoundary extends Component<Props, State> {
state: State = { hasError: false };
static getDerivedStateFromError(_error: Error): State {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo): void {
const report: ErrorReport = {
error: {
name: error.name,
message: error.message,
stack: error.stack,
},
componentStack: info.componentStack ?? '',
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
url: window.location.href,
};
// Send to your error reporting service
fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(report),
}).catch(() => {
// Silently fail — do not throw in error handler
});
}
render(): ReactNode {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}---
Pattern 8: React 19 Root-Level Error Callbacks
Complement error boundaries with global error monitoring:
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!, {
onCaughtError: (error, errorInfo) => {
// Fires when an Error Boundary catches an error
analytics.track('error_caught', {
message: error.message,
stack: errorInfo.componentStack,
});
},
onUncaughtError: (error, errorInfo) => {
// Fires when an error is NOT caught by any boundary
analytics.track('error_uncaught', {
message: error.message,
stack: errorInfo.componentStack,
severity: 'critical',
});
// Show global error overlay
showGlobalErrorOverlay(error);
},
onRecoverableError: (error, errorInfo) => {
// Fires when React recovers from an error automatically
// (e.g., hydration mismatch that falls back to client render)
analytics.track('error_recovered', {
message: error.message,
});
},
});
root.render(<App />);