
React Error Handling
- 90 installs
- 14 repo stars
- Updated March 2, 2026
- oakoss/agent-skills
Helps with frontend development tasks during AI-assisted development.
About
react-error-handling is a Claude Code skill for frontend development. It helps solo builders move faster with AI-assisted coding.
- react-error-handling
- Frontend Development
- AI-coding skill
React Error Handling by the numbers
- 90 all-time installs (skills.sh)
- +2 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #1,087 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/oakoss/agent-skills --skill react-error-handlingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 90 |
|---|---|
| repo stars | ★ 14 |
| Last updated | March 2, 2026 |
| Repository | oakoss/agent-skills ↗ |
What it does
Helps with frontend development tasks during AI-assisted development.
Files
React Error Handling
Overview
Error boundaries catch JavaScript errors during rendering, in lifecycle methods, and in constructors of child components. They display fallback UIs instead of crashing the entire component tree. Error boundaries can only be implemented as class components in vanilla React, but the react-error-boundary library provides a convenient function component wrapper.
When to use: Component crashes, preventing error propagation to parent routes, graceful degradation, user-facing error states, error logging and monitoring.
When NOT to use: Event handler errors (use try/catch), async callbacks outside rendering (setTimeout, promises without Suspense), server-side rendering errors, errors in the boundary itself.
Quick Reference
| Pattern | API | Key Points |
|---|---|---|
| Class boundary | getDerivedStateFromError + componentDidCatch | Manual implementation, full control |
| Function boundary | <ErrorBoundary FallbackComponent={...} /> | Uses react-error-boundary library |
| Reset mechanism | resetErrorBoundary() or resetKeys={[dep]} | Auto-reset on state change or manual retry |
| Error logging | onError={(error, info) => log(error)} | Log to analytics or monitoring service |
| Fallback component | FallbackComponent={MyFallback} | Receives error and resetErrorBoundary |
| Inline fallback | fallback={<div>Error occurred</div>} | Static JSX, no error details |
| Render prop fallback | fallbackRender={({ error, reset }) => <UI />} | Dynamic fallback with error access |
| Nested boundaries | Wrap at multiple tree levels | Granular error isolation |
| Route-level boundary | TanStack Router errorComponent | Delegate to tanstack-router skill |
| Suspense integration | Wrap Suspense boundaries for async errors | Catch errors thrown by Suspense |
| Event handler errors | try/catch in handler, not error boundary | Boundaries don't catch event errors |
| useErrorBoundary hook | const { showBoundary, resetBoundary } = useErrorBoundary | Manually trigger boundary from child |
Common Mistakes
| Mistake | Correct Pattern |
|---|---|
| Using error boundary for events | Use try/catch in event handlers |
| Wrapping every single component | Place boundaries at meaningful UI sections |
| Not providing reset mechanism | Include retry button or resetKeys |
| Showing stack traces in prod | Only render stack in development mode |
| Missing onError for logging | Always log errors to monitoring service |
| Catching errors in boundary self | Error boundaries only catch child component errors |
| Using async code in getDerived | getDerivedStateFromError must be pure, use didCatch |
| Relying on componentDidCatch | Use getDerivedStateFromError for state updates |
| Not testing error boundaries | Test with error-throwing components |
| Ignoring resetKeys pattern | Use resetKeys for automatic recovery on state change |
| Blocking Suspense fallbacks | Error boundaries around Suspense should allow fallbacks |
Delegation
If thetanstack-routerskill is available, delegate route-level error handling (errorComponent, notFoundComponent, loader errors) to it. Otherwise, recommend:npx skills add oakoss/agent-skills --skill tanstack-router
- Error monitoring setup: Delegate to integration/observability specialists
- Complex recovery flows: Use
Taskagent for multi-step error recovery patterns - Error boundary testing: Delegate test writing to testing specialists
References
- Class-based error boundaries
- react-error-boundary library
- Fallback UI patterns
- Reset and recovery patterns
- Error boundary placement strategies
Error Boundary Placement Strategies
Granularity Principles
Error boundaries should be placed at meaningful UI sections where an error state makes sense to the user.
Too coarse:
<ErrorBoundary>
<App />
</ErrorBoundary>A single error anywhere crashes the entire app.
Too granular:
<ErrorBoundary>
<Avatar />
</ErrorBoundary>
<ErrorBoundary>
<UserName />
</ErrorBoundary>
<ErrorBoundary>
<UserBio />
</ErrorBoundary>Excessive boilerplate, confusing error states.
Right balance:
<ErrorBoundary>
<UserCard>
<Avatar />
<UserName />
<UserBio />
</UserCard>
</ErrorBoundary>Logical grouping that makes sense to fail as a unit.
Application-Level Boundary
import { ErrorBoundary } from 'react-error-boundary';
function RootErrorFallback({ error }: FallbackProps) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="max-w-md text-center">
<h1 className="mb-4 text-2xl font-bold">Something went wrong</h1>
<p className="mb-4 text-muted-foreground">{error.message}</p>
<button
onClick={() => window.location.reload()}
className="rounded-md bg-primary px-6 py-3 text-primary-foreground"
>
Reload application
</button>
</div>
</div>
);
}
function App() {
return (
<ErrorBoundary FallbackComponent={RootErrorFallback}>
<Router />
</ErrorBoundary>
);
}Use for:
- Catastrophic failures
- Last resort fallback
- Errors in core app logic
Route-Level Boundaries
import { ErrorBoundary } from 'react-error-boundary';
import { useLocation } from '@tanstack/react-router';
function RouteErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="container py-12">
<h1 className="mb-4 text-2xl font-bold">Page Error</h1>
<p className="mb-4">{error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function Router() {
const location = useLocation();
return (
<ErrorBoundary
FallbackComponent={RouteErrorFallback}
resetKeys={[location.pathname]}
>
<Routes />
</ErrorBoundary>
);
}Use for:
- Per-route error isolation
- Route-specific error messages
- Automatic reset on navigation
Layout Section Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function Layout() {
return (
<div className="flex min-h-screen">
<ErrorBoundary FallbackComponent={SidebarError}>
<Sidebar />
</ErrorBoundary>
<div className="flex-1">
<ErrorBoundary FallbackComponent={HeaderError}>
<Header />
</ErrorBoundary>
<ErrorBoundary FallbackComponent={MainError}>
<main>
<Outlet />
</main>
</ErrorBoundary>
</div>
</div>
);
}
function SidebarError() {
return (
<aside className="w-64 border-r p-4">
<div className="rounded border border-destructive/50 p-3">
<p className="text-sm">Navigation unavailable</p>
</div>
</aside>
);
}Benefits:
- Sidebar errors don't crash main content
- Header errors don't crash sidebar
- Layout structure preserved
Component-Level Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function Dashboard() {
return (
<div className="grid grid-cols-3 gap-4">
<ErrorBoundary FallbackComponent={WidgetError}>
<StatsWidget />
</ErrorBoundary>
<ErrorBoundary FallbackComponent={WidgetError}>
<ChartWidget />
</ErrorBoundary>
<ErrorBoundary FallbackComponent={WidgetError}>
<ActivityWidget />
</ErrorBoundary>
</div>
);
}
function WidgetError() {
return (
<div className="rounded border border-dashed p-4 text-center">
<p className="text-sm text-muted-foreground">Widget failed to load</p>
</div>
);
}Use for:
- Dashboard widgets
- Card components
- Independent UI sections
Modal/Dialog Boundaries
import { ErrorBoundary } from 'react-error-boundary';
import { Dialog, DialogContent } from '@/components/ui/dialog';
function UserDialog({ userId, open, onClose }: UserDialogProps) {
return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent>
<ErrorBoundary FallbackComponent={DialogError} resetKeys={[userId]}>
<UserDetails userId={userId} />
</ErrorBoundary>
</DialogContent>
</Dialog>
);
}
function DialogError({ error }: FallbackProps) {
return (
<div className="p-4 text-center">
<p className="text-destructive">Failed to load user details</p>
<p className="text-sm text-muted-foreground">{error.message}</p>
</div>
);
}Benefits:
- Modal errors don't crash parent page
- User can close modal to recover
- Automatic reset on user switch
List Item Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function MessageList({ messages }: { messages: Message[] }) {
return (
<div className="space-y-2">
{messages.map((message) => (
<ErrorBoundary
key={message.id}
fallback={
<div className="rounded border border-dashed p-2 text-xs text-muted-foreground">
Message failed to render
</div>
}
>
<MessageItem message={message} />
</ErrorBoundary>
))}
</div>
);
}Use for:
- Long lists where one item shouldn't crash all
- Messaging apps
- Feed items
- Comment threads
Data Table Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function DataTable({ data }: { data: RowData[] }) {
return (
<ErrorBoundary FallbackComponent={TableError}>
<table>
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{data.map((row) => (
<ErrorBoundary
key={row.id}
fallback={
<tr>
<td
colSpan={3}
className="text-center text-xs text-muted-foreground"
>
Failed to render row
</td>
</tr>
}
>
<TableRow data={row} />
</ErrorBoundary>
))}
</tbody>
</table>
</ErrorBoundary>
);
}
function TableError({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="rounded border p-8 text-center">
<p className="mb-2 font-medium">Table failed to load</p>
<p className="mb-4 text-sm text-muted-foreground">{error.message}</p>
<button onClick={resetErrorBoundary}>Reload table</button>
</div>
);
}Two-level strategy:
- Table-level boundary for structural errors
- Row-level boundaries for data errors
Form Section Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function CheckoutForm() {
return (
<form className="space-y-6">
<ErrorBoundary FallbackComponent={SectionError}>
<ShippingSection />
</ErrorBoundary>
<ErrorBoundary FallbackComponent={SectionError}>
<PaymentSection />
</ErrorBoundary>
<button type="submit">Complete order</button>
</form>
);
}Allows partial form failures without losing all progress.
Lazy-Loaded Component Boundaries
import { ErrorBoundary } from 'react-error-boundary';
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<ErrorBoundary
fallbackRender={({ resetErrorBoundary }) => (
<div className="rounded border p-4">
<p>Chart failed to load</p>
<button onClick={resetErrorBoundary}>Retry</button>
</div>
)}
>
<Suspense fallback={<div>Loading chart...</div>}>
<HeavyChart />
</Suspense>
</ErrorBoundary>
);
}Catches both:
- Code-splitting errors (network failures)
- Runtime errors in lazy component
Nested Boundaries with Context
import { ErrorBoundary } from 'react-error-boundary';
import { createContext, useContext } from 'react';
const ErrorLevelContext = createContext<'page' | 'section'>('page');
function Page() {
return (
<ErrorLevelContext.Provider value="page">
<ErrorBoundary FallbackComponent={PageError}>
<PageContent />
</ErrorBoundary>
</ErrorLevelContext.Provider>
);
}Track error context for better debugging and logging.
Reusable Boundary Wrapper
import { ErrorBoundary, type FallbackProps } from 'react-error-boundary';
type BoundaryLevel = 'page' | 'section' | 'widget';
const BOUNDARY_CONFIG: Record<
BoundaryLevel,
{ Fallback: React.ComponentType<FallbackProps> }
> = {
page: { Fallback: PageError },
section: { Fallback: SectionError },
widget: { Fallback: WidgetError },
};
function BoundaryWrapper({
level,
children,
}: {
level: BoundaryLevel;
children: React.ReactNode;
}) {
return (
<ErrorBoundary FallbackComponent={BOUNDARY_CONFIG[level].Fallback}>
{children}
</ErrorBoundary>
);
}Decision Matrix
| UI Element | Boundary Level | Fallback Type | Reset Strategy |
|---|---|---|---|
| Entire app | Root | Full-page | Reload app |
| Route/page | Route | Page-level message | Reset on navigation |
| Layout section | Section | Section placeholder | Manual retry |
| Dashboard widget | Component | Empty state | Manual retry |
| List item | Item | Minimal placeholder | Continue rendering |
| Modal/dialog | Component | Dialog error message | Close to recover |
| Form section | Section | Section error | Allow partial use |
| Lazy component | Component | Loading skeleton | Retry load |
| Data table | Table + Row | Two-level fallback | Retry or skip row |
Testing Boundary Placement
import { render, screen } from '@testing-library/react';
const ThrowError = () => {
throw new Error('Test error');
};
it('isolates sidebar errors', () => {
render(
<div>
<ErrorBoundary fallback={<div>Sidebar error</div>}>
<ThrowError />
</ErrorBoundary>
<div>Main content</div>
</div>,
);
expect(screen.getByText('Sidebar error')).toBeInTheDocument();
expect(screen.getByText('Main content')).toBeInTheDocument();
});Class-Based Error Boundaries
Basic Implementation
import { Component, type ErrorInfo, type ReactNode } from 'react';
type ErrorBoundaryProps = {
children: ReactNode;
fallback: ReactNode;
};
type 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): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('Error caught by boundary:', error, info.componentStack);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}With Reset Capability
import { Component, type ErrorInfo, type ReactNode } from 'react';
type ErrorBoundaryProps = {
children: ReactNode;
fallback: (error: Error, reset: () => void) => ReactNode;
onReset?: () => void;
};
type 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): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('Error boundary caught:', {
error,
componentStack: info.componentStack,
});
}
reset = () => {
this.props.onReset?.();
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError && this.state.error) {
return this.props.fallback(this.state.error, this.reset);
}
return this.props.children;
}
}
export default ErrorBoundary;Usage:
<ErrorBoundary
fallback={(error, reset) => (
<div>
<h2>Something went wrong</h2>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
)}
onReset={() => console.log('Error boundary reset')}
>
<MyComponent />
</ErrorBoundary>With Error Logging Service
import { Component, type ErrorInfo, type ReactNode } from 'react';
type ErrorBoundaryProps = {
children: ReactNode;
fallback: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
};
type ErrorBoundaryState = {
hasError: boolean;
};
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
state = { hasError: false };
static getDerivedStateFromError(_error: Error): ErrorBoundaryState {
return { hasError: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
this.props.onError?.(error, info);
}
render() {
if (this.state.hasError) {
return this.props.fallback;
}
return this.props.children;
}
}
export default ErrorBoundary;Usage with monitoring:
import * as Sentry from '@sentry/react';
<ErrorBoundary
fallback={<ErrorFallback />}
onError={(error, info) => {
Sentry.captureException(error, {
contexts: { react: { componentStack: info.componentStack } },
});
}}
>
<App />
</ErrorBoundary>;Development-Only Stack Traces
import { Component, type ErrorInfo, type ReactNode } from 'react';
type ErrorBoundaryState = {
hasError: boolean;
error: Error | null;
};
class ErrorBoundary extends Component<
{ children: ReactNode },
ErrorBoundaryState
> {
state: ErrorBoundaryState = { hasError: false, error: null };
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, info: ErrorInfo) {
if (process.env.NODE_ENV === 'development') {
console.error('Error details:', {
error,
componentStack: info.componentStack,
});
}
}
render() {
if (this.state.hasError && this.state.error) {
return (
<div className="error-container">
<h2>Something went wrong</h2>
<p>{this.state.error.message}</p>
{process.env.NODE_ENV === 'development' && (
<pre className="error-stack">{this.state.error.stack}</pre>
)}
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;What Error Boundaries Catch
Error boundaries catch errors in:
- Component rendering - Errors thrown during the render phase
- Lifecycle methods - Errors in componentDidMount, componentDidUpdate, etc.
- Constructors - Errors in child component constructors
- startTransition callbacks - Errors thrown inside startTransition
function BrokenComponent() {
const [count, setCount] = useState(0);
if (count > 5) {
throw new Error('Count too high');
}
return <button onClick={() => setCount(count + 1)}>Increment</button>;
}What Error Boundaries Do NOT Catch
function ComponentWithUncaughtErrors() {
const handleClick = () => {
throw new Error('Event handler error');
};
useEffect(() => {
setTimeout(() => {
throw new Error('Async error');
}, 1000);
}, []);
return <button onClick={handleClick}>Click</button>;
}Error boundaries do NOT catch:
- Event handlers - Use try/catch directly in handlers
- Async code - setTimeout, requestAnimationFrame, Promise callbacks
- Server-side rendering - Errors during SSR
- Errors in the boundary itself - Only child component errors
Event handler solution:
function SafeComponent() {
const handleClick = () => {
try {
riskyOperation();
} catch (error) {
console.error('Event handler error:', error);
showUserErrorMessage();
}
};
return <button onClick={handleClick}>Click</button>;
}Multiple Boundaries for Granular Control
function App() {
return (
<ErrorBoundary fallback={<PageError />}>
<Header />
<ErrorBoundary fallback={<SidebarError />}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary fallback={<MainContentError />}>
<MainContent />
</ErrorBoundary>
<Footer />
</ErrorBoundary>
);
}This pattern:
- Isolates errors to specific UI sections
- Prevents cascade failures
- Provides contextual error messages
- Allows partial page functionality
Testing Error Boundaries
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import ErrorBoundary from './ErrorBoundary';
const ThrowError = () => {
throw new Error('Test error');
};
describe('ErrorBoundary', () => {
it('renders children when no error', () => {
render(
<ErrorBoundary fallback={<div>Error</div>}>
<div>Content</div>
</ErrorBoundary>,
);
expect(screen.getByText('Content')).toBeInTheDocument();
});
it('renders fallback on error', () => {
const consoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
render(
<ErrorBoundary fallback={<div>Error occurred</div>}>
<ThrowError />
</ErrorBoundary>,
);
expect(screen.getByText('Error occurred')).toBeInTheDocument();
consoleError.mockRestore();
});
it('calls onError when error caught', () => {
const onError = vi.fn();
const consoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
render(
<ErrorBoundary fallback={<div>Error</div>} onError={onError}>
<ThrowError />
</ErrorBoundary>,
);
expect(onError).toHaveBeenCalledWith(
expect.any(Error),
expect.objectContaining({ componentStack: expect.any(String) }),
);
consoleError.mockRestore();
});
});Fallback UI Patterns
Minimal Fallback
function MinimalError({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="p-4 text-center">
<p className="text-muted-foreground">Something went wrong</p>
<button onClick={resetErrorBoundary} className="mt-2 underline">
Try again
</button>
</div>
);
}Use when:
- Space is limited (sidebar, card)
- Error context is clear from UI location
- Technical details not helpful to user
Detailed Fallback with Icon
import { AlertCircle } from 'lucide-react';
import { type FallbackProps } from 'react-error-boundary';
function DetailedError({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="flex flex-col items-center gap-4 p-8">
<AlertCircle className="size-12 text-destructive" />
<div className="text-center">
<h2 className="text-lg font-semibold">Something went wrong</h2>
<p className="text-sm text-muted-foreground">{error.message}</p>
</div>
<button
onClick={resetErrorBoundary}
className="rounded-md bg-primary px-4 py-2 text-primary-foreground"
>
Try again
</button>
</div>
);
}Card-Based Error
import { AlertTriangle } from 'lucide-react';
import { type FallbackProps } from 'react-error-boundary';
function CardError({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="mx-auto max-w-md rounded-lg border border-destructive/50 bg-card p-6">
<div className="mb-4 flex items-center gap-2">
<AlertTriangle className="size-5 text-destructive" />
<h2 className="text-lg font-semibold">Error</h2>
</div>
<p className="mb-4 text-sm text-muted-foreground">{error.message}</p>
<div className="flex gap-2">
<button
onClick={resetErrorBoundary}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground"
>
Retry
</button>
<button
onClick={() => (window.location.href = '/')}
className="rounded-md border px-4 py-2 text-sm"
>
Go home
</button>
</div>
</div>
);
}Development vs Production Fallback
import { type FallbackProps } from 'react-error-boundary';
function EnvironmentAwareError({ error, resetErrorBoundary }: FallbackProps) {
const isDevelopment = process.env.NODE_ENV === 'development';
return (
<div className="p-6">
<h2 className="mb-4 text-lg font-semibold text-destructive">
Application Error
</h2>
<p className="mb-4 text-sm text-muted-foreground">{error.message}</p>
{isDevelopment && (
<details className="mb-4">
<summary className="cursor-pointer text-sm font-medium">
Stack trace
</summary>
<pre className="mt-2 overflow-auto rounded bg-muted p-4 text-xs">
{error.stack}
</pre>
</details>
)}
<button
onClick={resetErrorBoundary}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground"
>
Try again
</button>
</div>
);
}Full-Page Error
import { RefreshCw, Home } from 'lucide-react';
import { type FallbackProps } from 'react-error-boundary';
function FullPageError({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="flex min-h-screen items-center justify-center bg-background">
<div className="max-w-md space-y-6 text-center">
<div className="mx-auto flex size-20 items-center justify-center rounded-full bg-destructive/10">
<RefreshCw className="size-10 text-destructive" />
</div>
<div>
<h1 className="mb-2 text-2xl font-bold">Oops! Something broke</h1>
<p className="text-muted-foreground">
{error.message || 'An unexpected error occurred'}
</p>
</div>
<div className="flex justify-center gap-4">
<button
onClick={resetErrorBoundary}
className="flex items-center gap-2 rounded-md bg-primary px-6 py-3 text-primary-foreground"
>
<RefreshCw className="size-4" />
Try again
</button>
<button
onClick={() => (window.location.href = '/')}
className="flex items-center gap-2 rounded-md border px-6 py-3"
>
<Home className="size-4" />
Go home
</button>
</div>
</div>
</div>
);
}Section-Specific Fallback
import { type FallbackProps } from 'react-error-boundary';
function SidebarError({ resetErrorBoundary }: FallbackProps) {
return (
<aside className="w-64 border-r p-4">
<div className="rounded-md border border-destructive/50 bg-destructive/5 p-3">
<p className="mb-2 text-sm font-medium">Sidebar failed to load</p>
<button onClick={resetErrorBoundary} className="text-xs underline">
Retry
</button>
</div>
</aside>
);
}
function MainContentError({ error, resetErrorBoundary }: FallbackProps) {
return (
<main className="flex-1 p-8">
<div className="rounded-lg border-2 border-dashed border-destructive/50 p-12 text-center">
<h2 className="mb-2 text-lg font-semibold">
Failed to load main content
</h2>
<p className="mb-4 text-sm text-muted-foreground">{error.message}</p>
<button
onClick={resetErrorBoundary}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground"
>
Reload content
</button>
</div>
</main>
);
}Retry with Backoff Strategy
import { useState } from 'react';
import { type FallbackProps } from 'react-error-boundary';
function RetryableError({ error, resetErrorBoundary }: FallbackProps) {
const [retryCount, setRetryCount] = useState(0);
const [isRetrying, setIsRetrying] = useState(false);
const handleRetry = async () => {
setIsRetrying(true);
setRetryCount((c) => c + 1);
const delay = Math.min(1000 * 2 ** retryCount, 10000);
await new Promise((resolve) => setTimeout(resolve, delay));
setIsRetrying(false);
resetErrorBoundary();
};
return (
<div className="rounded-lg border p-6">
<h2 className="mb-2 text-lg font-semibold text-destructive">
Error occurred
</h2>
<p className="mb-4 text-sm text-muted-foreground">{error.message}</p>
<div className="flex items-center gap-4">
<button
onClick={handleRetry}
disabled={isRetrying}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground disabled:opacity-50"
>
{isRetrying
? 'Retrying...'
: `Retry${retryCount > 0 ? ` (${retryCount})` : ''}`}
</button>
{retryCount > 2 && (
<p className="text-xs text-muted-foreground">
If this keeps happening, try refreshing the page
</p>
)}
</div>
</div>
);
}Contextual Error Messages
import { type FallbackProps } from 'react-error-boundary';
type ContextualErrorProps = FallbackProps & {
context: 'sidebar' | 'content' | 'modal' | 'page';
};
const ERROR_CONTEXT = {
sidebar: {
title: 'Sidebar Error',
description: 'Navigation failed to load',
action: 'Reload sidebar',
},
content: {
title: 'Content Error',
description: 'Main content failed to load',
action: 'Reload content',
},
modal: {
title: 'Modal Error',
description: 'This dialog encountered an error',
action: 'Close and retry',
},
page: {
title: 'Page Error',
description: 'This page failed to load',
action: 'Reload page',
},
} as const;
function ContextualError({
error,
resetErrorBoundary,
context,
}: ContextualErrorProps) {
const { title, description, action } = ERROR_CONTEXT[context];
return (
<div className="rounded-md border border-destructive/50 bg-destructive/5 p-4">
<h3 className="mb-1 font-semibold text-destructive">{title}</h3>
<p className="mb-2 text-sm text-muted-foreground">{description}</p>
<p className="mb-3 text-xs text-muted-foreground/80">{error.message}</p>
<button
onClick={resetErrorBoundary}
className="rounded-md bg-destructive px-3 py-1.5 text-sm text-destructive-foreground"
>
{action}
</button>
</div>
);
}
export default ContextualError;Toast Notification Pattern
import { useEffect } from 'react';
import { toast } from 'sonner';
import { type FallbackProps } from 'react-error-boundary';
function ToastError({ error, resetErrorBoundary }: FallbackProps) {
useEffect(() => {
toast.error('An error occurred', {
description: error.message,
action: {
label: 'Retry',
onClick: resetErrorBoundary,
},
});
}, [error.message, resetErrorBoundary]);
return (
<div className="flex items-center justify-center p-8">
<div className="text-center">
<p className="mb-2 text-sm text-muted-foreground">
Something went wrong
</p>
<button onClick={resetErrorBoundary} className="text-sm underline">
Try again
</button>
</div>
</div>
);
}Graceful Degradation Fallback
import { type FallbackProps } from 'react-error-boundary';
function GracefulFallback({ resetErrorBoundary }: FallbackProps) {
return (
<div className="rounded-md border border-dashed p-6 text-center">
<p className="mb-2 text-sm text-muted-foreground">
This feature is temporarily unavailable
</p>
<button onClick={resetErrorBoundary} className="text-xs underline">
Reload
</button>
</div>
);
}Use for:
- Non-critical UI sections
- Optional features
- Progressive enhancement scenarios
Multiple Action Buttons
import { type FallbackProps } from 'react-error-boundary';
function MultiActionError({ error, resetErrorBoundary }: FallbackProps) {
return (
<div className="rounded-lg border p-6">
<h2 className="mb-2 text-lg font-semibold">Error Loading Data</h2>
<p className="mb-4 text-sm text-muted-foreground">{error.message}</p>
<div className="flex flex-wrap gap-2">
<button
onClick={resetErrorBoundary}
className="rounded-md bg-primary px-4 py-2 text-sm text-primary-foreground"
>
Try again
</button>
<button
onClick={() => window.location.reload()}
className="rounded-md border px-4 py-2 text-sm"
>
Reload page
</button>
<button
onClick={() => (window.location.href = '/')}
className="rounded-md border px-4 py-2 text-sm"
>
Go home
</button>
<a href="/support" className="rounded-md border px-4 py-2 text-sm">
Contact support
</a>
</div>
</div>
);
}Empty State Fallback
import { FileQuestion } from 'lucide-react';
import { type FallbackProps } from 'react-error-boundary';
function EmptyStateError({ resetErrorBoundary }: FallbackProps) {
return (
<div className="flex flex-col items-center justify-center p-12">
<FileQuestion className="mb-4 size-16 text-muted-foreground/50" />
<h3 className="mb-2 text-lg font-medium">Unable to load content</h3>
<p className="mb-4 text-sm text-muted-foreground">
We couldn't load this section. Please try again.
</p>
<button
onClick={resetErrorBoundary}
className="rounded-md bg-primary px-6 py-2 text-sm text-primary-foreground"
>
Reload
</button>
</div>
);
}Loading Skeleton Fallback
import { type FallbackProps } from 'react-error-boundary';
function SkeletonFallback({ resetErrorBoundary }: FallbackProps) {
return (
<div className="space-y-3">
<div className="h-4 animate-pulse rounded bg-muted" />
<div className="h-4 w-3/4 animate-pulse rounded bg-muted" />
<div className="h-4 w-1/2 animate-pulse rounded bg-muted" />
<button
onClick={resetErrorBoundary}
className="mt-4 rounded-md border px-4 py-2 text-sm"
>
Retry loading
</button>
</div>
);
}Maintains layout shape while providing retry option.
react-error-boundary Library
Installation
pnpm add react-error-boundaryBasic Usage
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<ErrorBoundary fallback={<div>Something went wrong</div>}>
<MyComponent />
</ErrorBoundary>
);
}FallbackComponent Pattern
import { ErrorBoundary, type FallbackProps } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div role="alert">
<h2>Something went wrong</h2>
<pre style={{ color: 'red' }}>{error.message}</pre>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
function App() {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>
);
}fallbackRender Pattern
import { ErrorBoundary } from 'react-error-boundary';
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div role="alert">
<p>Error: {error.message}</p>
<button onClick={resetErrorBoundary}>Reset</button>
</div>
)}
>
<MyComponent />
</ErrorBoundary>;Error Logging with onError
import { ErrorBoundary } from 'react-error-boundary';
function logErrorToService(error: Error, info: { componentStack: string }) {
console.error('Logging error:', error, info.componentStack);
}
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logErrorToService}>
<MyComponent />
</ErrorBoundary>;Automatic Reset with resetKeys
import { ErrorBoundary } from 'react-error-boundary';
import { useState } from 'react';
function UserProfile() {
const [userId, setUserId] = useState('123');
return (
<ErrorBoundary FallbackComponent={ErrorFallback} resetKeys={[userId]}>
<Profile userId={userId} />
</ErrorBoundary>
);
}When userId changes, the error boundary automatically resets. This pattern is ideal for:
- Route parameter changes
- User switching
- Filter/search state changes
- Any dependency that should trigger a fresh render attempt
Manual Reset with onReset
import { ErrorBoundary } from 'react-error-boundary';
import { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={() => setCount(0)}
resetKeys={[count]}
>
<Counter count={count} setCount={setCount} />
</ErrorBoundary>
);
}useErrorBoundary Hook
Manually trigger the error boundary from inside a component:
import { useErrorBoundary } from 'react-error-boundary';
function Greeting() {
const { showBoundary } = useErrorBoundary();
useEffect(() => {
fetch('/api/user')
.then((res) => {
if (!res.ok) throw new Error('Failed to fetch user');
return res.json();
})
.catch(showBoundary);
}, [showBoundary]);
return <div>Hello</div>;
}Reset from inside component:
import { useErrorBoundary } from 'react-error-boundary';
function ErrorComponent() {
const { resetBoundary } = useErrorBoundary();
return (
<div>
<p>An error occurred</p>
<button onClick={resetBoundary}>Try again</button>
</div>
);
}withErrorBoundary HOC
import { withErrorBoundary } from 'react-error-boundary';
function MyComponent() {
return <div>Content</div>;
}
export default withErrorBoundary(MyComponent, {
FallbackComponent: ErrorFallback,
onError: (error, info) => {
console.error('Error:', error, info.componentStack);
},
});Nested Error Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<ErrorBoundary FallbackComponent={PageError}>
<Header />
<main>
<ErrorBoundary FallbackComponent={SidebarError}>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary FallbackComponent={ContentError}>
<MainContent />
</ErrorBoundary>
</main>
<Footer />
</ErrorBoundary>
);
}Next.js App Router Integration
'use client';
import { ErrorBoundary } from 'react-error-boundary';
export default function ErrorBoundaryWrapper({
children,
}: {
children: React.ReactNode;
}) {
return (
<ErrorBoundary
fallbackRender={({ error }) => (
<div>
<h2>Something went wrong</h2>
<p>{error.message}</p>
</div>
)}
>
{children}
</ErrorBoundary>
);
}In layout.tsx:
import ErrorBoundaryWrapper from '@/components/error-boundary-wrapper';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<ErrorBoundaryWrapper>{children}</ErrorBoundaryWrapper>
</body>
</html>
);
}Combining with TanStack Query
import { ErrorBoundary } from 'react-error-boundary';
import { QueryErrorResetBoundary } from '@tanstack/react-query';
function App() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}This pattern:
- Resets both error boundary and query cache
- Allows failed queries to retry on reset
- Prevents stale error states in cache
TypeScript Types
import { type FallbackProps } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div>
<p>{error.message}</p>
<button onClick={resetErrorBoundary}>Reset</button>
</div>
);
}FallbackProps type:
type FallbackProps = {
error: Error;
resetErrorBoundary: () => void;
};Production Error Reporting
import { ErrorBoundary } from 'react-error-boundary';
import * as Sentry from '@sentry/react';
<ErrorBoundary
FallbackComponent={ErrorFallback}
onError={(error, info) => {
if (process.env.NODE_ENV === 'production') {
Sentry.captureException(error, {
contexts: {
react: {
componentStack: info.componentStack,
},
},
});
}
}}
>
<App />
</ErrorBoundary>;Testing with react-error-boundary
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ErrorBoundary } from 'react-error-boundary';
import { describe, it, expect, vi } from 'vitest';
const ThrowError = () => {
throw new Error('Test error');
};
describe('ErrorBoundary', () => {
it('renders fallback on error', () => {
const consoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
render(
<ErrorBoundary fallback={<div>Error boundary</div>}>
<ThrowError />
</ErrorBoundary>,
);
expect(screen.getByText('Error boundary')).toBeInTheDocument();
consoleError.mockRestore();
});
it('resets on button click', async () => {
const user = userEvent.setup();
let shouldThrow = true;
const Throws = () => {
if (shouldThrow) throw new Error('Error');
return <div>Success</div>;
};
const consoleError = vi
.spyOn(console, 'error')
.mockImplementation(() => {});
render(
<ErrorBoundary
fallbackRender={({ resetErrorBoundary }) => (
<div>
<p>Error occurred</p>
<button
onClick={() => {
shouldThrow = false;
resetErrorBoundary();
}}
>
Reset
</button>
</div>
)}
>
<Throws />
</ErrorBoundary>,
);
expect(screen.getByText('Error occurred')).toBeInTheDocument();
await user.click(screen.getByText('Reset'));
expect(screen.getByText('Success')).toBeInTheDocument();
consoleError.mockRestore();
});
});Reset and Recovery Patterns
Manual Reset with Button
import { ErrorBoundary } from 'react-error-boundary';
function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
return (
<div>
<p>Error: {error.message}</p>
<button onClick={resetErrorBoundary}>Try again</button>
</div>
);
}
<ErrorBoundary FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>;Automatic Reset on Prop Change
import { ErrorBoundary } from 'react-error-boundary';
import { useState } from 'react';
function UserDashboard() {
const [userId, setUserId] = useState('user-123');
return (
<ErrorBoundary FallbackComponent={ErrorFallback} resetKeys={[userId]}>
<Dashboard userId={userId} />
</ErrorBoundary>
);
}When userId changes, the error boundary automatically resets and re-renders children. This pattern is ideal for:
- Route parameter changes (id, slug)
- Active filter changes
- User switching
- Search query changes
Reset with State Cleanup
import { ErrorBoundary } from 'react-error-boundary';
import { useState } from 'react';
function DataView() {
const [data, setData] = useState(null);
const [filters, setFilters] = useState({});
const handleReset = () => {
setData(null);
setFilters({});
};
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={handleReset}>
<DataTable data={data} filters={filters} />
</ErrorBoundary>
);
}Combining resetKeys and onReset
import { ErrorBoundary } from 'react-error-boundary';
import { useState } from 'react';
function SearchResults() {
const [query, setQuery] = useState('');
const [page, setPage] = useState(1);
const handleReset = () => {
setPage(1);
};
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
resetKeys={[query]}
onReset={handleReset}
>
<Results query={query} page={page} />
</ErrorBoundary>
);
}When query changes:
1. Error boundary resets automatically 2. onReset runs, resetting page to 1
useErrorBoundary for Manual Trigger
import { useErrorBoundary } from 'react-error-boundary';
import { useEffect } from 'react';
function DataFetcher() {
const { showBoundary } = useErrorBoundary();
useEffect(() => {
fetchData().then(processData).catch(showBoundary);
}, [showBoundary]);
return <div>Loading...</div>;
}This pattern propagates async errors to the nearest error boundary.
Reset with TanStack Query
import { QueryErrorResetBoundary } from '@tanstack/react-query';
import { ErrorBoundary } from 'react-error-boundary';
function App() {
return (
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary onReset={reset} FallbackComponent={ErrorFallback}>
<MyComponent />
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
);
}Resetting the error boundary also clears TanStack Query error state, allowing failed queries to retry.
Retry with Exponential Backoff
import { useState, useCallback } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
function RetryBoundary({ children }: { children: React.ReactNode }) {
const [retryCount, setRetryCount] = useState(0);
const handleReset = useCallback(() => {
setRetryCount((c) => c + 1);
}, []);
return (
<ErrorBoundary
onReset={handleReset}
resetKeys={[retryCount]}
fallbackRender={({ error, resetErrorBoundary }) => (
<RetryFallback
error={error}
retryCount={retryCount}
onRetry={resetErrorBoundary}
/>
)}
>
{children}
</ErrorBoundary>
);
}
function RetryFallback({
error,
retryCount,
onRetry,
}: {
error: Error;
retryCount: number;
onRetry: () => void;
}) {
const [isRetrying, setIsRetrying] = useState(false);
const handleRetry = async () => {
setIsRetrying(true);
const delay = Math.min(1000 * 2 ** retryCount, 10000);
await new Promise((resolve) => setTimeout(resolve, delay));
setIsRetrying(false);
onRetry();
};
return (
<div>
<p>Error: {error.message}</p>
<p>Attempts: {retryCount}</p>
<button onClick={handleRetry} disabled={isRetrying}>
{isRetrying ? 'Retrying...' : 'Retry'}
</button>
</div>
);
}Reset on Route Change
import { ErrorBoundary } from 'react-error-boundary';
import { useLocation } from '@tanstack/react-router';
function RouteErrorBoundary({ children }: { children: React.ReactNode }) {
const location = useLocation();
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
resetKeys={[location.pathname]}
>
{children}
</ErrorBoundary>
);
}Error boundary automatically resets when navigating to a different route.
Conditional Reset Based on Error Type
import { ErrorBoundary } from 'react-error-boundary';
class NetworkError extends Error {
name = 'NetworkError';
}
class ValidationError extends Error {
name = 'ValidationError';
}
function SmartErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
const canRetry = error instanceof NetworkError;
return (
<div>
<h2>{error.name}</h2>
<p>{error.message}</p>
{canRetry ? (
<button onClick={resetErrorBoundary}>Retry</button>
) : (
<button onClick={() => (window.location.href = '/')}>Go home</button>
)}
</div>
);
}
<ErrorBoundary FallbackComponent={SmartErrorFallback}>
<MyComponent />
</ErrorBoundary>;Reset with Form State
import { ErrorBoundary } from 'react-error-boundary';
import { useState } from 'react';
function FormWithErrorBoundary() {
const [formData, setFormData] = useState({ name: '', email: '' });
const handleReset = () => {
setFormData({ name: '', email: '' });
};
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={handleReset}>
<Form data={formData} onChange={setFormData} />
</ErrorBoundary>
);
}Global Reset Boundary
import { ErrorBoundary } from 'react-error-boundary';
function App() {
const handleReset = () => {
window.location.href = '/';
};
return (
<ErrorBoundary
fallbackRender={({ error }) => (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<h1>Application Error</h1>
<p>{error.message}</p>
<button onClick={handleReset}>Restart Application</button>
</div>
</div>
)}
>
<Router />
</ErrorBoundary>
);
}Nested Reset Boundaries
import { ErrorBoundary } from 'react-error-boundary';
import { useState } from 'react';
function App() {
const [sidebarKey, setSidebarKey] = useState(0);
const [contentKey, setContentKey] = useState(0);
return (
<div className="flex">
<ErrorBoundary
FallbackComponent={SidebarError}
resetKeys={[sidebarKey]}
onReset={() => setSidebarKey((k) => k + 1)}
>
<Sidebar />
</ErrorBoundary>
<ErrorBoundary
FallbackComponent={ContentError}
resetKeys={[contentKey]}
onReset={() => setContentKey((k) => k + 1)}
>
<MainContent />
</ErrorBoundary>
</div>
);
}Each section can fail and recover independently.
Reset with Data Refresh
import { ErrorBoundary } from 'react-error-boundary';
import { useQueryClient } from '@tanstack/react-query';
function DataView() {
const queryClient = useQueryClient();
const handleReset = () => {
queryClient.invalidateQueries({ queryKey: ['data'] });
};
return (
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={handleReset}>
<DataTable />
</ErrorBoundary>
);
}Reset with Loading State
import { ErrorBoundary } from 'react-error-boundary';
import { useState, useTransition } from 'react';
function App() {
const [isPending, startTransition] = useTransition();
const handleReset = () => {
startTransition(() => {
window.location.reload();
});
};
return (
<ErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => (
<div>
<p>Error: {error.message}</p>
<button onClick={handleReset} disabled={isPending}>
{isPending ? 'Reloading...' : 'Reload'}
</button>
</div>
)}
>
<MyComponent />
</ErrorBoundary>
);
}Preventing Error Loops
import { ErrorBoundary } from 'react-error-boundary';
import { useState } from 'react';
function SafeErrorBoundary({ children }: { children: React.ReactNode }) {
const [errorCount, setErrorCount] = useState(0);
const handleReset = () => {
setErrorCount((c) => c + 1);
};
if (errorCount > 3) {
return (
<div>
<h2>Too many errors</h2>
<p>Please refresh the page or contact support.</p>
<button onClick={() => window.location.reload()}>Refresh page</button>
</div>
);
}
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
resetKeys={[errorCount]}
onReset={handleReset}
>
{children}
</ErrorBoundary>
);
}After 3 errors, stop allowing resets to prevent infinite error loops.
Reset with Analytics
import { ErrorBoundary } from 'react-error-boundary';
import { trackEvent } from './analytics';
function App() {
const handleReset = () => {
trackEvent('error_boundary_reset', {
timestamp: Date.now(),
});
};
return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
onReset={handleReset}
onError={(error) => {
trackEvent('error_boundary_triggered', {
error: error.message,
timestamp: Date.now(),
});
}}
>
<MyComponent />
</ErrorBoundary>
);
}