home / skills / agents-inc / skills / web-error-handling-error-boundaries
This skill helps you implement robust React error boundaries with fallback UIs, recovery resets, and centralized logging for resilient UI.
npx playbooks add skill agents-inc/skills --skill web-error-handling-error-boundariesReview the files below or copy the command above to add this skill to your agents.
---
name: web-error-handling-error-boundaries
description: Error boundary patterns, fallback UI, reset/retry functionality, react-error-boundary library
---
# React Error Boundaries
> **Quick Guide:** Error boundaries catch JavaScript errors in component trees and display fallback UI. Use `react-error-boundary` library for production apps. Place boundaries strategically around features (not just root). Boundaries do NOT catch event handler, async, or SSR errors. **React 19+**: Use `createRoot` options (`onCaughtError`, `onUncaughtError`, `onRecoverableError`) for centralized error logging.
---
<critical_requirements>
## CRITICAL: Before Using This Skill
> **All code must follow project conventions in CLAUDE.md** (kebab-case, named exports, import ordering, `import type`, named constants)
**(You MUST use `getDerivedStateFromError` for rendering fallback UI - it runs during render phase)**
**(You MUST use `componentDidCatch` for side effects like logging - it runs during commit phase)**
**(You MUST wrap error boundaries around feature sections, not just the app root)**
**(You MUST provide reset/retry functionality for recoverable errors)**
**(You MUST use `role="alert"` on fallback UI for accessibility)**
**(You MUST use `createRoot` error options (`onCaughtError`, `onUncaughtError`, `onRecoverableError`) for centralized error logging in React 19+)**
</critical_requirements>
---
**Auto-detection:** error boundary, ErrorBoundary, getDerivedStateFromError, componentDidCatch, fallback UI, react-error-boundary, useErrorBoundary, error recovery, error fallback, onCaughtError, onUncaughtError, onRecoverableError, captureOwnerStack
**When to use:**
- Catching and displaying fallback UI for render errors
- Implementing retry/reset functionality after errors
- Preventing entire app crashes from component failures
- Creating isolated failure domains for different features
**Key patterns covered:**
- Class-based error boundary implementation
- `react-error-boundary` library patterns
- `useErrorBoundary` hook for async error handling
- Fallback UI with reset functionality
- Strategic boundary placement (granular vs coarse)
- TypeScript error boundary patterns
- **React 19+**: `createRoot` error options (`onCaughtError`, `onUncaughtError`, `onRecoverableError`)
- **React 19+**: `captureOwnerStack()` for enhanced debugging
**When NOT to use:**
- Event handler errors (use try/catch)
- Async code errors outside components (use try/catch)
- Server-side rendering errors (handle at framework level)
- API request errors (handle in data fetching layer)
**Detailed Resources:**
- For code examples, see [examples/core.md](examples/core.md)
- For React 19 error hooks, see [examples/react-19-hooks.md](examples/react-19-hooks.md)
- For testing patterns, see [examples/testing.md](examples/testing.md)
- For decision frameworks and anti-patterns, see [reference.md](reference.md)
---
<philosophy>
## Philosophy
Error boundaries provide **graceful degradation** - when one component fails, the rest of the application continues working. The key principle is **isolation**: wrap distinct features in separate boundaries so failures are contained. Error boundaries are the ONLY way to catch errors during React rendering; they don't replace try/catch for imperative code but complement it for declarative UI.
**Core principles:**
1. **Isolation over global handling** - Multiple granular boundaries beat one root boundary
2. **Recovery over failure** - Provide reset/retry when possible
3. **User feedback over silent failure** - Show meaningful fallback UI
4. **Logging integration** - Pass errors to monitoring via `onError` callback
5. **Centralized observability (React 19+)** - Use `createRoot` error options for unified error tracking across all boundaries
</philosophy>
---
<patterns>
## Core Patterns
### Pattern 1: Class-Based Error Boundary (Native React)
Error boundaries MUST be class components because `getDerivedStateFromError` and `componentDidCatch` have no hook equivalents.
#### Two Lifecycle Methods
| Method | Phase | Purpose | Side Effects |
| -------------------------- | ------ | ----------------------------- | ------------ |
| `getDerivedStateFromError` | Render | Update state to show fallback | NOT allowed |
| `componentDidCatch` | Commit | Log errors, call callbacks | Allowed |
#### Implementation
```typescript
// ✅ Good Example - Complete error boundary with reset
import { Component } from "react";
import type { ErrorInfo, ReactNode } from "react";
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode);
onError?: (error: Error, errorInfo: ErrorInfo) => void;
onReset?: () => void;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
// Update state so next render shows fallback UI
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
// Side effects allowed here - call logging callback
this.props.onError?.(error, errorInfo);
}
handleReset = (): void => {
this.props.onReset?.();
this.setState({ hasError: false, error: null });
};
render(): ReactNode {
const { hasError, error } = this.state;
const { children, fallback } = this.props;
if (hasError && error) {
// Support both static and function fallback
if (typeof fallback === "function") {
return fallback(error, this.handleReset);
}
if (fallback) {
return fallback;
}
// Default fallback
return (
<div role="alert">
<h2>Something went wrong</h2>
<button onClick={this.handleReset}>Try again</button>
</div>
);
}
return children;
}
}
```
**Why good:** Separates render-phase logic (getDerivedStateFromError) from commit-phase side effects (componentDidCatch), provides reset capability for recovery, flexible fallback API supports both static and function patterns, onError callback enables logging integration without coupling to specific tools
---
### Pattern 2: react-error-boundary Library
The `react-error-boundary` library provides a production-ready error boundary with hooks support and additional features.
#### Installation
```bash
npm install react-error-boundary
```
#### ErrorBoundary Component Props
| Prop | Type | Purpose |
| ------------------- | ----------------------- | ------------------------------- |
| `fallback` | `ReactNode` | Static fallback UI |
| `FallbackComponent` | `ComponentType` | Component that renders fallback |
| `fallbackRender` | `(props) => ReactNode` | Render prop for fallback |
| `onError` | `(error, info) => void` | Error logging callback |
| `onReset` | `(details) => void` | Called when boundary resets |
| `resetKeys` | `unknown[]` | Dependencies that trigger reset |
#### Basic Usage
```typescript
// ✅ Good Example - react-error-boundary with FallbackComponent
import { ErrorBoundary } from "react-error-boundary";
import type { FallbackProps } from "react-error-boundary";
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert">
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, info) => {
// Send to your error tracking service
console.error("Boundary caught:", error, info);
}}
onReset={() => {
// Reset app state if needed
}}
>
<Dashboard />
</ErrorBoundary>
);
}
```
**Why good:** FallbackComponent pattern enables reusable fallback UI, onError callback decouples error handling from logging implementation, onReset allows cleaning up state before retry
---
### Pattern 3: useErrorBoundary Hook (Async Errors)
Error boundaries don't catch async errors by default. Use `useErrorBoundary` hook to manually trigger the nearest error boundary.
#### The Problem
```typescript
// ❌ This error is NOT caught by error boundary
async function handleClick() {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error("API failed"); // Lost - boundary doesn't see it
}
}
```
#### The Solution
```typescript
// ✅ Good Example - useErrorBoundary for async errors
import { useState } from "react";
import { useErrorBoundary } from "react-error-boundary";
function DataLoader() {
const { showBoundary } = useErrorBoundary();
const [data, setData] = useState<unknown>(null);
const handleLoadData = async () => {
try {
const response = await fetch("/api/data");
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (error) {
// Manually trigger nearest error boundary
showBoundary(error);
}
};
return (
<div>
<button onClick={handleLoadData}>Load Data</button>
{data && <pre>{JSON.stringify(data, null, 2)}</pre>}
</div>
);
}
// Wrap with ErrorBoundary
<ErrorBoundary FallbackComponent={ErrorFallback}>
<DataLoader />
</ErrorBoundary>
```
**Why good:** showBoundary manually propagates async errors to the nearest boundary, maintains consistent error handling UX across sync and async failures, keeps error handling logic in the error boundary system
#### When to Use showBoundary
- Async operations that should show fallback UI on failure
- Event handlers where errors should propagate to boundary
- Effects that should trigger error UI on failure
#### When NOT to Use showBoundary
- Errors that should be handled locally with inline UI
- Recoverable errors that don't need full fallback UI
- Validation errors that need field-level feedback
---
### Pattern 4: resetKeys for Automatic Reset
Use `resetKeys` to automatically reset the error boundary when certain values change.
```typescript
// ✅ Good Example - Reset boundary when route changes
import { ErrorBoundary } from "react-error-boundary";
import { useLocation } from "react-router-dom";
function AppWithRouteReset() {
const location = useLocation();
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
resetKeys={[location.pathname]}
onResetKeysChange={() => {
// Optionally track resets
}}
>
<Routes />
</ErrorBoundary>
);
}
```
**Why good:** Automatic reset when navigation occurs prevents stale error states, users don't need to manually retry after navigating away and back
#### Common resetKeys Patterns
| Pattern | Use Case |
| -------------- | --------------------------------- |
| `[pathname]` | Reset on route change |
| `[selectedId]` | Reset when viewing different item |
| `[retryCount]` | Reset after programmatic retry |
| `[queryKey]` | Reset when data source changes |
---
### Pattern 5: Granular Boundary Placement
Place error boundaries strategically to isolate failures.
#### Placement Strategy
```
App
├─ ErrorBoundary (root - catches unhandled errors)
│ ├─ Header
│ ├─ ErrorBoundary (sidebar - isolated)
│ │ └─ Sidebar
│ ├─ ErrorBoundary (main content - isolated)
│ │ └─ MainContent
│ │ ├─ ErrorBoundary (widget A)
│ │ │ └─ ChartWidget
│ │ └─ ErrorBoundary (widget B)
│ │ └─ TableWidget
│ └─ Footer
```
```typescript
// ✅ Good Example - Granular boundaries around features
function Dashboard() {
return (
<div>
<ErrorBoundary
fallback={<div>Chart unavailable</div>}
onError={logError}
>
<ChartWidget />
</ErrorBoundary>
<ErrorBoundary
fallback={<div>Table unavailable</div>}
onError={logError}
>
<DataTable />
</ErrorBoundary>
<ErrorBoundary
fallback={<div>Stats unavailable</div>}
onError={logError}
>
<StatsPanel />
</ErrorBoundary>
</div>
);
}
```
**Why good:** One widget failing doesn't crash the entire dashboard, each feature has contextual fallback UI, errors are isolated to their domain
```typescript
// ❌ Bad Example - Single boundary for everything
function Dashboard() {
return (
<ErrorBoundary fallback={<div>Dashboard error</div>}>
<ChartWidget />
<DataTable />
<StatsPanel />
</ErrorBoundary>
);
}
```
**Why bad:** One failing widget crashes the entire dashboard, users lose access to working features, poor user experience
---
### Pattern 6: Fallback UI Patterns
Design fallback UI that provides context and recovery options.
#### Minimal Fallback
```typescript
// ✅ Good Example - Minimal fallback with retry
function MinimalFallback({ resetErrorBoundary }: FallbackProps) {
return (
<div role="alert">
<p>Failed to load</p>
<button onClick={resetErrorBoundary}>Retry</button>
</div>
);
}
```
#### Detailed Fallback
```typescript
// ✅ Good Example - Detailed fallback with error info
function DetailedFallback({ error, resetErrorBoundary }: FallbackProps) {
const isDev = process.env.NODE_ENV === "development";
return (
<div role="alert">
<h2>Something went wrong</h2>
<p>We're working on fixing this issue.</p>
{isDev && (
<details>
<summary>Error details</summary>
<pre>{error.message}</pre>
<pre>{error.stack}</pre>
</details>
)}
<div>
<button onClick={resetErrorBoundary}>Try again</button>
<button onClick={() => window.location.reload()}>
Refresh page
</button>
</div>
</div>
);
}
```
**Why good:** role="alert" announces error to screen readers, error details in development aid debugging, production users see friendly message without technical details, multiple recovery options
#### Feature-Specific Fallback
```typescript
// ✅ Good Example - Contextual fallback
function ChartFallback({ resetErrorBoundary }: FallbackProps) {
return (
<div role="alert" className="chart-fallback">
<span aria-hidden="true">📊</span>
<p>Chart could not be displayed</p>
<button onClick={resetErrorBoundary}>Reload chart</button>
</div>
);
}
// Usage
<ErrorBoundary FallbackComponent={ChartFallback}>
<RevenueChart />
</ErrorBoundary>
```
**Why good:** Fallback UI matches the context of the failed component, users understand what feature is unavailable
---
### Pattern 7: React 19+ Error Hooks (createRoot Options)
React 19 introduces three new root-level error handlers that complement error boundaries. These run at the `createRoot` level and provide centralized error logging.
#### Three Error Handlers
| Handler | When Called | Use Case |
| -------------------- | --------------------------------- | ----------------------------------------- |
| `onCaughtError` | Error caught by an Error Boundary | Log errors that are handled by boundaries |
| `onUncaughtError` | Error NOT caught by any boundary | Log/report fatal errors |
| `onRecoverableError` | React auto-recovers from error | Log hydration mismatches, suspense errors |
#### Basic Setup
```typescript
// ✅ Good Example - React 19 createRoot with error handlers
import { createRoot } from "react-dom/client";
const ROOT_ELEMENT_ID = "root";
function logError(
error: Error,
errorInfo: { componentStack?: string | null }
) {
// Send to your error tracking service
console.error("React error:", error);
console.error("Component stack:", errorInfo.componentStack);
}
const container = document.getElementById(ROOT_ELEMENT_ID);
if (!container) throw new Error("Root element not found");
const root = createRoot(container, {
onCaughtError: (error, errorInfo) => {
// Error caught by an Error Boundary
logError(error, errorInfo);
},
onUncaughtError: (error, errorInfo) => {
// Error NOT caught - fatal
logError(error, errorInfo);
},
onRecoverableError: (error, errorInfo) => {
// React auto-recovered (e.g., hydration mismatch)
logError(error, errorInfo);
},
});
root.render(<App />);
```
**Why good:** Centralized error logging for ALL React errors, captures errors even when no boundary catches them, provides component stack for debugging
#### With Error Monitoring Service
```typescript
// ✅ Good Example - Integration with error monitoring
import { createRoot } from "react-dom/client";
import * as Sentry from "@sentry/react";
const ROOT_ELEMENT_ID = "root";
const container = document.getElementById(ROOT_ELEMENT_ID);
if (!container) throw new Error("Root element not found");
const root = createRoot(container, {
onCaughtError: Sentry.reactErrorHandler((error, errorInfo) => {
// Custom handling for caught errors (optional)
console.warn("Caught error:", error.message);
}),
onUncaughtError: Sentry.reactErrorHandler((error, errorInfo) => {
// Custom handling for uncaught errors (optional)
console.error("Uncaught error:", error.message);
}),
onRecoverableError: Sentry.reactErrorHandler(),
});
root.render(<App />);
```
**Why good:** Error monitoring integration at root level, single point of configuration, `Sentry.reactErrorHandler()` adds proper React context to reports
> **See [examples/react-19-hooks.md](examples/react-19-hooks.md) for complete examples including `captureOwnerStack()` and advanced patterns.**
</patterns>
---
<integration>
## Integration Points
**Error boundaries integrate with your application through callbacks:**
- **`onError`**: Pass errors to your logging/monitoring system
- **`onReset`**: Clean up application state before retry
- **`resetKeys`**: Sync boundary state with application state (routes, selections)
**Error boundaries work alongside:**
- **Try/catch**: For event handlers, async code, imperative operations
- **Data fetching error states**: For API-level errors with retry logic
- **Form validation**: For field-level error display
**Boundaries do NOT replace these patterns - they complement them for rendering errors.**
</integration>
---
<critical_reminders>
## CRITICAL REMINDERS
> **All code must follow project conventions in CLAUDE.md**
**(You MUST use `getDerivedStateFromError` for rendering fallback UI - it runs during render phase)**
**(You MUST use `componentDidCatch` for side effects like logging - it runs during commit phase)**
**(You MUST wrap error boundaries around feature sections, not just the app root)**
**(You MUST provide reset/retry functionality for recoverable errors)**
**(You MUST use `role="alert"` on fallback UI for accessibility)**
**(You MUST use `createRoot` error options (`onCaughtError`, `onUncaughtError`, `onRecoverableError`) for centralized error logging in React 19+)**
**Failure to follow these rules will result in poor error handling, inaccessible UIs, or unrecoverable error states.**
</critical_reminders>
This skill documents robust React error boundary patterns and practical fallback UI strategies using native class-based boundaries and the react-error-boundary library. It emphasizes recoverable error flows, accessibility, and centralized logging hooks available in React 19+. The goal is to help teams isolate failures, provide retry/reset experience, and integrate with observability tools.
The skill explains class-based ErrorBoundary implementations that use getDerivedStateFromError for render-phase state updates and componentDidCatch for commit-phase side effects like logging. It covers the react-error-boundary package (ErrorBoundary, FallbackComponent, useErrorBoundary) for hook-friendly workflows and useErrorBoundary/showBoundary to surface async or event-handler errors to the nearest boundary. It also documents React 19+ createRoot error options for centralized error reporting.
Do error boundaries catch async or event handler errors?
No—by default they only catch render, lifecycle, and constructor errors. Use showBoundary (useErrorBoundary) or try/catch for async and event handler errors.
Where should I place boundaries in my app?
Prefer granular placement around features or widgets to isolate failures. Use a root boundary as a safety net but avoid relying on a single global boundary.