From web-error-handling-error-boundaries
Error boundary patterns, fallback UI, reset/retry, react-error-boundary library, React 19 createRoot error hooks
How this skill is triggered — by the user, by Claude, or both
Slash command
/web-error-handling-error-boundaries:web-error-handling-error-boundariesThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Quick Guide:** Error boundaries catch JavaScript errors in component trees and display fallback UI. Use `react-error-boundary` library (v6+) for production apps. Place boundaries strategically around features, not just root. Boundaries do NOT catch event handler, async, or SSR errors -- use `showBoundary()` hook for async. **React 19+**: Use `createRoot` options (`onCaughtError`, `onUncaugh...
Quick Guide: Error boundaries catch JavaScript errors in component trees and display fallback UI. Use
react-error-boundarylibrary (v6+) for production apps. Place boundaries strategically around features, not just root. Boundaries do NOT catch event handler, async, or SSR errors -- useshowBoundary()hook for async. React 19+: UsecreateRootoptions (onCaughtError,onUncaughtError,onRecoverableError) for centralized error logging.
<critical_requirements>
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)
</critical_requirements>
Auto-detection: error boundary, ErrorBoundary, getDerivedStateFromError, componentDidCatch, fallback UI, react-error-boundary, useErrorBoundary, showBoundary, error recovery, error fallback, onCaughtError, onUncaughtError, onRecoverableError, captureOwnerStack, FallbackProps, resetKeys
When to use:
Key patterns covered:
react-error-boundary library patterns (v6+)useErrorBoundary hook with showBoundary() for async errorsrole="alert"resetKeys for automatic boundary resetcreateRoot error options for centralized loggingcaptureOwnerStack() for enhanced debuggingWhen NOT to use:
Detailed Resources:
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 complement try/catch for imperative code.
Core principles:
onError callbackcreateRoot error options for unified error trackingError boundaries MUST be class components -- getDerivedStateFromError and componentDidCatch have no hook equivalents.
| Method | Phase | Purpose | Side Effects |
|---|---|---|---|
getDerivedStateFromError | Render | Update state to show fallback | NOT allowed |
componentDidCatch | Commit | Log errors, call callbacks | Allowed |
// ✅ Good - 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 {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
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) {
if (typeof fallback === "function") return fallback(error, this.handleReset);
if (fallback) return fallback;
return (
<div role="alert">
<h2>Something went wrong</h2>
<button onClick={this.handleReset}>Try again</button>
</div>
);
}
return children;
}
}
Why good: Render-phase/commit-phase separation, reset capability, flexible fallback API, onError enables logging without coupling to specific tools
Production-ready error boundary with hooks support, resetKeys, and useErrorBoundary.
npm install react-error-boundary
| 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 |
// ✅ Good - FallbackComponent pattern
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>
);
}
export function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, info) => {
// Send to your error monitoring service
console.error("Boundary caught:", error, info);
}}
>
<Dashboard />
</ErrorBoundary>
);
}
Why good: Reusable FallbackComponent, onError decouples logging, onReset enables state cleanup
See examples/core.md for resetKeys, useErrorBoundary, and granular placement examples.
Error boundaries don't catch async errors. Use showBoundary() from useErrorBoundary to manually trigger the nearest boundary.
// ❌ This async error is NOT caught by error boundary
async function handleClick() {
throw new Error("API failed"); // Lost - boundary doesn't see it
}
// ✅ Good - showBoundary propagates async errors
import { useErrorBoundary } from "react-error-boundary";
function DataLoader() {
const { showBoundary } = useErrorBoundary();
const handleLoadData = async () => {
try {
const response = await fetch("/api/data");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
// ... handle success
} catch (error) {
showBoundary(error); // Manually trigger nearest boundary
}
};
return <button onClick={handleLoadData}>Load Data</button>;
}
Why good: Propagates async errors to boundary, consistent error UI across sync/async failures
Use showBoundary for: Async operations, event handlers, effects that should show fallback UI on failure. Do NOT use for: Errors handled locally with inline UI, validation errors needing field-level feedback.
Use resetKeys to auto-reset the boundary when certain values change (e.g., route, selected item).
// ✅ Good - Reset boundary on route change
<ErrorBoundary
FallbackComponent={ErrorFallback}
resetKeys={[location.pathname]}
>
<Routes />
</ErrorBoundary>
| Pattern | Use Case |
|---|---|
[pathname] | Reset on route change |
[selectedId] | Reset when viewing different item |
[retryCount] | Reset after programmatic retry |
Gotcha: resetKeys comparison is shallow -- objects/arrays need stable references.
App
├─ ErrorBoundary (root - last-resort catch-all)
│ ├─ Header
│ ├─ ErrorBoundary (sidebar)
│ │ └─ Sidebar
│ ├─ ErrorBoundary (main content)
│ │ ├─ ErrorBoundary (widget A)
│ │ │ └─ ChartWidget
│ │ └─ ErrorBoundary (widget B)
│ │ └─ TableWidget
│ └─ Footer
// ✅ Good - Granular boundaries isolate failures
function Dashboard() {
return (
<div>
<ErrorBoundary fallback={<div>Chart unavailable</div>} onError={logError}>
<ChartWidget />
</ErrorBoundary>
<ErrorBoundary fallback={<div>Table unavailable</div>} onError={logError}>
<DataTable />
</ErrorBoundary>
</div>
);
}
Why good: One widget failing doesn't crash the dashboard, each feature has contextual fallback
// ❌ Bad - Single boundary for everything
<ErrorBoundary fallback={<div>Dashboard error</div>}>
<ChartWidget />
<DataTable />
<StatsPanel />
</ErrorBoundary>
Why bad: One failing widget crashes entire dashboard, users lose access to working features
Fallback UI must include role="alert" for accessibility, retry button for recovery, and hide error details in production.
// ✅ Good - Environment-aware fallback with accessibility
function DetailedFallback({ error, resetErrorBoundary }: FallbackProps) {
const isDev = process.env.NODE_ENV === "development";
return (
<div role="alert">
<h2>Something went wrong</h2>
{isDev && (
<details>
<summary>Error details</summary>
<pre>{error.message}</pre>
</details>
)}
<button onClick={resetErrorBoundary}>Try again</button>
<button onClick={() => window.location.reload()}>Refresh page</button>
</div>
);
}
Why good: role="alert" announces to screen readers, dev-only details, multiple recovery options
// ❌ Bad - Missing accessibility, raw errors in production
<div>
<pre>{error.stack}</pre>
<span onClick={reset}>Retry</span> {/* Not keyboard accessible */}
</div>
Why bad: No role="alert", exposes internals to users, span not keyboard-accessible
React 19 adds three root-level error handlers for centralized logging. These complement (not replace) ErrorBoundary components.
| Handler | When Called | Use Case |
|---|---|---|
onCaughtError | Error caught by an ErrorBoundary | Log handled errors |
onUncaughtError | Error NOT caught by any boundary | Log fatal errors |
onRecoverableError | React auto-recovers from error | Log hydration mismatches, suspense errors |
// ✅ Good - Centralized error logging with createRoot
import { createRoot } from "react-dom/client";
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: (error, errorInfo) => {
reportToMonitoring("caught", error, errorInfo.componentStack);
},
onUncaughtError: (error, errorInfo) => {
reportToMonitoring("uncaught", error, errorInfo.componentStack);
},
onRecoverableError: (error, errorInfo) => {
reportToMonitoring("recoverable", error, errorInfo.componentStack);
},
});
root.render(<App />);
Why good: Single configuration point for all React error logging, catches errors that escape all boundaries
See examples/react-19-hooks.md for
captureOwnerStack(), error filtering, and hydrateRoot patterns.
<red_flags>
High Priority:
role="alert" on fallback -- screen readers don't announce errorsgetDerivedStateFromError -- violates React phase rulesMedium Priority:
showBoundary() for async errors -- they silently failonError callback -- errors not reported to monitoringGotchas & Edge Cases:
getDerivedStateFromError runs during render -- no side effects allowedresetKeys comparison is shallow -- objects/arrays need stable referencescaptureOwnerStack() returns null in productiononCaughtError runs AFTER boundary's componentDidCatch, not beforeonRecoverableError may have error.cause with the original thrown error</red_flags>
<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)
Failure to follow these rules will result in poor error handling, inaccessible UIs, or unrecoverable error states.
</critical_reminders>
npx claudepluginhub agents-inc/skills --plugin web-error-handling-error-boundariesReviews error-boundary placement, fallback UX, and failure-isolation strategy to prevent component errors from crashing the entire page.
Implements error handling patterns, exception filters, error boundaries, retry logic, and recovery strategies for React, Next.js, and NestJS applications.
Builds resilient applications with proper error handling using Result types, error boundaries, try-catch patterns, typed errors, and graceful degradation.