E

Error Boundary & Recovery Patterns

Implements comprehensive error handling patterns including React error boundaries, API error responses, retry logic, and graceful degradation.

SkillClipticsdevelopmentv1.0.0MIT
0 views0 copies

Error Boundary & Recovery Patterns

Implement robust error handling strategies across React components and backend services with graceful degradation, automatic recovery, and user-friendly fallback UIs.

When to Use This Template

Choose Error Boundary & Recovery Patterns when:

  • React components crash and take down the entire page instead of showing fallbacks
  • You need systematic error handling with retry logic and circuit breakers
  • API failures should degrade gracefully rather than showing blank screens
  • You want consistent error reporting and recovery across your application

Consider alternatives when:

  • You need global error monitoring and alerting (use Sentry or Datadog)
  • Your errors are purely validation-related (use form validation libraries)
  • You need distributed tracing across microservices (use OpenTelemetry)

Quick Start

# .claude/skills/error-boundary-recovery-patterns.yml name: error-boundary-recovery-patterns description: Implement error boundaries and recovery strategies prompt: | Add error handling with recovery patterns to the specified code. Implement: 1. React Error Boundaries with fallback UIs 2. Retry logic with exponential backoff 3. Circuit breaker pattern for failing services 4. Graceful degradation for non-critical features 5. Error reporting with context Prioritize user experience — show helpful messages, not stack traces.

Example invocation:

claude "Add error boundaries to the Dashboard page with retry capability for failed API calls"

Generated Error Boundary:

function DashboardErrorFallback({ error, resetErrorBoundary }) { return ( <div className="p-6 bg-red-50 rounded-lg text-center"> <h3 className="text-lg font-semibold text-red-800"> Dashboard failed to load </h3> <p className="text-red-600 mt-2"> {error.message || "An unexpected error occurred"} </p> <button onClick={resetErrorBoundary} className="mt-4 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700" > Try Again </button> </div> ); }

Core Concepts

Error Handling Strategy Matrix

Error TypeStrategyUser ImpactRecovery
Render errorError Boundary + fallback UIComponent-levelManual retry
API timeoutRetry with backoffLoading stateAutomatic
Service downCircuit breaker + cacheStale data shownAutomatic
Auth expiredToken refresh + replayTransparentAutomatic
Network offlineQueue + sync laterOffline bannerOn reconnect

React Error Boundary with Recovery

import { Component, ReactNode } from 'react'; interface Props { children: ReactNode; fallback: (props: { error: Error; retry: () => void }) => ReactNode; onError?: (error: Error, errorInfo: React.ErrorInfo) => void; isolationLevel?: 'page' | 'section' | 'component'; } interface State { error: Error | null; errorCount: number; } class RecoverableErrorBoundary extends Component<Props, State> { state: State = { error: null, errorCount: 0 }; static getDerivedStateFromError(error: Error): Partial<State> { return { error }; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { this.setState(prev => ({ errorCount: prev.errorCount + 1 })); this.props.onError?.(error, errorInfo); // Report to error tracking service console.error(`[ErrorBoundary:${this.props.isolationLevel}]`, { error: error.message, componentStack: errorInfo.componentStack, recoveryAttempts: this.state.errorCount }); } handleRetry = () => { this.setState({ error: null }); }; render() { if (this.state.error) { if (this.state.errorCount > 3) { return <PermanentErrorFallback error={this.state.error} />; } return this.props.fallback({ error: this.state.error, retry: this.handleRetry }); } return this.props.children; } }

Retry with Exponential Backoff

async function withRetry<T>( fn: () => Promise<T>, options: { maxRetries?: number; baseDelay?: number; maxDelay?: number; retryableErrors?: (error: Error) => boolean; } = {} ): Promise<T> { const { maxRetries = 3, baseDelay = 1000, maxDelay = 10000, retryableErrors = (err) => { // Retry network errors and 5xx, not 4xx if (err.name === 'TypeError') return true; // network error const status = (err as any).status; return status >= 500 || status === 429; } } = options; let lastError: Error; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (attempt === maxRetries || !retryableErrors(lastError)) { throw lastError; } const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay); const jitter = delay * (0.5 + Math.random() * 0.5); await new Promise(resolve => setTimeout(resolve, jitter)); } } throw lastError!; } // Usage const data = await withRetry(() => fetch('/api/dashboard').then(r => r.json()), { maxRetries: 3, baseDelay: 500 });

Circuit Breaker Pattern

class CircuitBreaker { private failures = 0; private lastFailure = 0; private state: 'closed' | 'open' | 'half-open' = 'closed'; constructor( private threshold: number = 5, private resetTimeout: number = 30000 ) {} async execute<T>(fn: () => Promise<T>, fallback?: () => T): Promise<T> { if (this.state === 'open') { if (Date.now() - this.lastFailure > this.resetTimeout) { this.state = 'half-open'; } else if (fallback) { return fallback(); } else { throw new Error('Circuit breaker is open'); } } try { const result = await fn(); this.onSuccess(); return result; } catch (error) { this.onFailure(); if (fallback) return fallback(); throw error; } } private onSuccess() { this.failures = 0; this.state = 'closed'; } private onFailure() { this.failures++; this.lastFailure = Date.now(); if (this.failures >= this.threshold) { this.state = 'open'; } } }

Configuration

OptionTypeDefaultDescription
maxRetriesnumber3Maximum retry attempts before giving up
backoffBasenumber1000Base delay in ms for exponential backoff
circuitThresholdnumber5Failures before circuit breaker opens
circuitResetMsnumber30000Time before circuit breaker tests again
isolationLevelstring"section"Error boundary scope: page, section, component
reportErrorsbooleantrueSend errors to tracking service

Best Practices

  1. Nest error boundaries at multiple levels — Use a page-level boundary to catch catastrophic failures and section-level boundaries to isolate independent features. If the comments section crashes, the main content should still render. Never rely on a single top-level boundary.

  2. Distinguish retryable from permanent errors — Network timeouts and 503s are retryable. A 404 or 422 validation error will fail every time. Check the error type before retrying to avoid wasting time and API quota on guaranteed failures.

  3. Show loading states during retry, not errors — When automatically retrying a failed request, keep showing the loading spinner rather than flashing an error message for 500ms. Only show the error UI after all retry attempts are exhausted.

  4. Add jitter to backoff delays — When multiple clients retry simultaneously (thundering herd), identical backoff timing causes them to all retry at the same moment. Adding random jitter (50-100% of the delay) spreads retries across time and reduces server load.

  5. Cache the last successful response as fallback — When a circuit breaker trips open, serve the last cached successful response with a "data may be stale" indicator rather than showing a blank error state. Stale data is almost always better than no data for read-only endpoints.

Common Issues

Error boundary not catching async errors — React Error Boundaries only catch errors during rendering, lifecycle methods, and constructors. They do not catch errors in event handlers, async code, or setTimeout callbacks. Wrap async operations in try/catch and use state to trigger boundary re-renders via setState that throws.

Infinite retry loop consuming resources — The retry function keeps attempting requests against a permanently down service, consuming bandwidth and battery. Always set a maximum retry count and implement a circuit breaker that stops retries entirely after repeated failures. Combine with exponential backoff to give the service time to recover.

Component re-mounts losing user input after recovery — When the error boundary re-renders after a retry, the child component tree re-mounts from scratch, losing form input, scroll position, and local state. Lift critical state above the error boundary or persist it to sessionStorage before the error boundary resets, then restore it on re-mount.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates