git:20260202.b236384 to git:20260316.00cb75b
121 added, 282 removed. Audit A to A.
---
name: web-error-handling-error-boundaries
- description: Error boundary patterns, fallback UI, reset/retry functionality, react-error-boundary library
+ description: Error boundary patterns, fallback UI, reset/retry, react-error-boundary library, React 19 createRoot error hooks
---
# 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.
+ > **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`, `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
+ **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:**
- 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
+ - `react-error-boundary` library patterns (v6+)
+ - `useErrorBoundary` hook with `showBoundary()` for async errors
+ - Fallback UI with reset functionality and `role="alert"`
- Strategic boundary placement (granular vs coarse)
- - TypeScript error boundary patterns
- - **React 19+**: `createRoot` error options (`onCaughtError`, `onUncaughtError`, `onRecoverableError`)
+ - `resetKeys` for automatic boundary reset
+ - **React 19+**: `createRoot` error options for centralized logging
- **React 19+**: `captureOwnerStack()` for enhanced debugging
**When NOT to use:**
- Event handler errors (use try/catch)
- - Async code errors outside components (use try/catch)
+ - Async code errors outside components (use try/catch or showBoundary)
- Server-side rendering errors (handle at framework level)
- - API request errors (handle in data fetching layer)
+ - API request errors (handle in your 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)
+ - [examples/core.md](examples/core.md) - Complete boundary implementations, library usage, granular placement
+ - [examples/react-19-hooks.md](examples/react-19-hooks.md) - createRoot error options, captureOwnerStack, error filtering
+ - [examples/recovery.md](examples/recovery.md) - Retry limits, exponential backoff, error classification
+ - [examples/testing.md](examples/testing.md) - Testing boundaries, async errors, resetKeys
+ - [reference.md](reference.md) - Decision frameworks, anti-patterns, checklists
---
<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.
+ 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:**
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
+ 3. **User feedback over silent failure** - Show meaningful, accessible 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
+ 5. **Centralized observability (React 19+)** - Use `createRoot` error options for unified error tracking
</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.
+ Error boundaries MUST be class components -- `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
+ // ✅ 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 {
- // 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
+ 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:** 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
+ **Why good:** Render-phase/commit-phase separation, reset capability, flexible fallback API, onError enables logging 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.
+ ### Pattern 2: react-error-boundary Library (v6+)
- #### Installation
+ Production-ready error boundary with hooks support, resetKeys, and `useErrorBoundary`.
```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
+ // ✅ 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>
);
}
- function App() {
+ export function App() {
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, info) => {
- // Send to your error tracking service
+ // Send to your error monitoring 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
+ **Why good:** Reusable FallbackComponent, onError decouples logging, onReset enables state cleanup
+ > See [examples/core.md](examples/core.md) for resetKeys, useErrorBoundary, and granular placement examples.
+
---
### 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
+ Error boundaries don't catch async errors. Use `showBoundary()` from `useErrorBoundary` to manually trigger the nearest boundary.
```typescript
- // ❌ This error is NOT caught by error boundary
+ // ❌ This async 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
- }
+ throw new Error("API failed"); // Lost - boundary doesn't see it
}
```
- #### The Solution
-
```typescript
- // ✅ Good Example - useErrorBoundary for async errors
- import { useState } from "react";
+ // ✅ Good - showBoundary propagates async errors
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);
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
+ // ... handle success
} catch (error) {
- // Manually trigger nearest error boundary
- showBoundary(error);
+ showBoundary(error); // Manually trigger nearest boundary
}
};
- return (
- <div>
- <button onClick={handleLoadData}>Load Data</button>
- {data && <pre>{JSON.stringify(data, null, 2)}</pre>}
- </div>
- );
+ return <button onClick={handleLoadData}>Load Data</button>;
}
-
- // 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
+ **Why good:** Propagates async errors to boundary, consistent error UI across sync/async failures
- - 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
+ **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.
---
### Pattern 4: resetKeys for Automatic Reset
- Use `resetKeys` to automatically reset the error boundary when certain values change.
+ Use `resetKeys` to auto-reset the boundary when certain values change (e.g., route, selected item).
```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>
- );
- }
+ // ✅ Good - Reset boundary on route change
+ <ErrorBoundary
+ FallbackComponent={ErrorFallback}
+ resetKeys={[location.pathname]}
+ >
+ <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 |
+ **Gotcha:** `resetKeys` comparison is shallow -- objects/arrays need stable references.
+
---
### Pattern 5: Granular Boundary Placement
- Place error boundaries strategically to isolate failures.
-
- #### Placement Strategy
-
```
App
- ├─ ErrorBoundary (root - catches unhandled errors)
+ ├─ ErrorBoundary (root - last-resort catch-all)
│ ├─ Header
- │ ├─ ErrorBoundary (sidebar - isolated)
+ │ ├─ ErrorBoundary (sidebar)
│ │ └─ Sidebar
- │ ├─ ErrorBoundary (main content - isolated)
- │ │ └─ MainContent
- │ │ ├─ ErrorBoundary (widget A)
- │ │ │ └─ ChartWidget
- │ │ └─ ErrorBoundary (widget B)
- │ │ └─ TableWidget
+ │ ├─ ErrorBoundary (main content)
+ │ │ ├─ ErrorBoundary (widget A)
+ │ │ │ └─ ChartWidget
+ │ │ └─ ErrorBoundary (widget B)
+ │ │ └─ TableWidget
│ └─ Footer
```
```typescript
- // ✅ Good Example - Granular boundaries around features
+ // ✅ Good - Granular boundaries isolate failures
function Dashboard() {
return (
<div>
- <ErrorBoundary
- fallback={<div>Chart unavailable</div>}
- onError={logError}
- >
+ <ErrorBoundary fallback={<div>Chart unavailable</div>} onError={logError}>
<ChartWidget />
</ErrorBoundary>
-
- <ErrorBoundary
- fallback={<div>Table unavailable</div>}
- onError={logError}
- >
+ <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
+ **Why good:** One widget failing doesn't crash the dashboard, each feature has contextual fallback
```typescript
- // ❌ Bad Example - Single boundary for everything
- function Dashboard() {
- return (
- <ErrorBoundary fallback={<div>Dashboard error</div>}>
- <ChartWidget />
- <DataTable />
- <StatsPanel />
- </ErrorBoundary>
- );
- }
+ // ❌ Bad - Single boundary for everything
+ <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
+ **Why bad:** One failing widget crashes entire dashboard, users lose access to working features
---
- ### 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>
- );
- }
- ```
+ ### Pattern 6: Fallback UI
- #### Detailed Fallback
+ Fallback UI must include `role="alert"` for accessibility, retry button for recovery, and hide error details in production.
```typescript
- // ✅ Good Example - Detailed fallback with error info
+ // ✅ 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>
- <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>
+ <button onClick={resetErrorBoundary}>Try again</button>
+ <button onClick={() => window.location.reload()}>Refresh page</button>
</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
+ **Why good:** `role="alert"` announces to screen readers, dev-only details, multiple recovery options
```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>
+ // ❌ Bad - Missing accessibility, raw errors in production
+ <div>
+ <pre>{error.stack}</pre>
+ <span onClick={reset}>Retry</span> {/* Not keyboard accessible */}
+ </div>
```
- **Why good:** Fallback UI matches the context of the failed component, users understand what feature is unavailable
+ **Why bad:** No `role="alert"`, exposes internals to users, `span` not keyboard-accessible
---
- ### 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
+ ### Pattern 7: React 19+ createRoot Error Options
- | 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 |
+ React 19 adds three root-level error handlers for centralized logging. These complement (not replace) ErrorBoundary components.
- #### Basic Setup
+ | 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 |
```typescript
- // ✅ Good Example - React 19 createRoot with error handlers
+ // ✅ Good - Centralized error logging with createRoot
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);
+ reportToMonitoring("caught", error, errorInfo.componentStack);
},
onUncaughtError: (error, errorInfo) => {
- // Error NOT caught - fatal
- logError(error, errorInfo);
+ reportToMonitoring("uncaught", error, errorInfo.componentStack);
},
onRecoverableError: (error, errorInfo) => {
- // React auto-recovered (e.g., hydration mismatch)
- logError(error, errorInfo);
+ reportToMonitoring("recoverable", error, errorInfo.componentStack);
},
});
-
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
+ **Why good:** Single configuration point for all React error logging, catches errors that escape all boundaries
- > **See [examples/react-19-hooks.md](examples/react-19-hooks.md) for complete examples including `captureOwnerStack()` and advanced patterns.**
+ > See [examples/react-19-hooks.md](examples/react-19-hooks.md) for `captureOwnerStack()`, error filtering, and hydrateRoot patterns.
</patterns>
---
- <integration>
+ <red_flags>
- ## Integration Points
+ ## RED FLAGS
- **Error boundaries integrate with your application through callbacks:**
+ **High Priority:**
- - **`onError`**: Pass errors to your logging/monitoring system
- - **`onReset`**: Clean up application state before retry
- - **`resetKeys`**: Sync boundary state with application state (routes, selections)
+ - Missing error boundaries entirely -- app crashes on any render error
+ - Single root boundary only -- no isolation between features
+ - No reset/retry functionality -- users must refresh page
+ - Missing `role="alert"` on fallback -- screen readers don't announce errors
+ - Side effects in `getDerivedStateFromError` -- violates React phase rules
- **Error boundaries work alongside:**
+ **Medium Priority:**
- - **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
+ - Not using `showBoundary()` for async errors -- they silently fail
+ - Same fallback for all boundaries -- no context about what failed
+ - No `onError` callback -- errors not reported to monitoring
+ - Overly granular boundaries (every component) -- unnecessary overhead
- **Boundaries do NOT replace these patterns - they complement them for rendering errors.**
+ **Gotchas & Edge Cases:**
- </integration>
+ - `getDerivedStateFromError` runs during render -- no side effects allowed
+ - Error boundaries don't catch errors in **themselves** -- only children
+ - Nested boundaries: **innermost** boundary catches first
+ - Hot reload can trigger boundaries in development (expected behavior)
+ - `resetKeys` comparison is shallow -- objects/arrays need stable references
+ - SSR hydration errors may not be caught by client-side boundaries
+ - **React 19:** `captureOwnerStack()` returns `null` in production
+ - **React 19:** `onCaughtError` runs AFTER boundary's `componentDidCatch`, not before
+ - **React 19:** `onRecoverableError` may have `error.cause` with the original thrown error
+ - **React 19:** These options are silently ignored on React 18
+ </red_flags>
+
---
<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>