Error Boundaries
Catch and handle errors in component trees gracefully
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI.
Implementation
1// Error Boundary Implementation2import React, { Component, ErrorInfo, ReactNode } from 'react';34interface Props {5 children: ReactNode;6 fallback?: ReactNode;7 onError?: (error: Error, errorInfo: ErrorInfo) => void;8}910interface State {11 hasError: boolean;12 error: Error | null;13 errorInfo: ErrorInfo | null;14}1516class ErrorBoundary extends Component<Props, State> {17 constructor(props: Props) {18 super(props);19 this.state = {20 hasError: false,21 error: null,22 errorInfo: null23 };24 }2526 static getDerivedStateFromError(error: Error): State {27 // Update state so the next render will show the fallback UI28 return {29 hasError: true,30 error,31 errorInfo: null32 };33 }3435 componentDidCatch(error: Error, errorInfo: ErrorInfo) {36 // Log error to error reporting service37 console.error('Error caught by boundary:', error, errorInfo);3839 // Update state with error details40 this.setState({41 error,42 errorInfo43 });4445 // Call custom error handler if provided46 this.props.onError?.(error, errorInfo);4748 // Send to error tracking service49 if (typeof window !== 'undefined' && window.errorTracker) {50 window.errorTracker.logError(error, {51 componentStack: errorInfo.componentStack,52 props: this.props53 });54 }55 }5657 render() {58 if (this.state.hasError) {59 // Custom fallback UI60 if (this.props.fallback) {61 return <>{this.props.fallback}</>;62 }6364 // Default error UI65 return (66 <div className="error-boundary-default">67 <h2>Oops! Something went wrong</h2>68 <details style={{ whiteSpace: 'pre-wrap' }}>69 <summary>Click for details</summary>70 {this.state.error && this.state.error.toString()}71 <br />72 {this.state.errorInfo && this.state.errorInfo.componentStack}73 </details>74 <button onClick={() => window.location.reload()}>75 Reload Page76 </button>77 </div>78 );79 }8081 return this.props.children;82 }83}8485// Hook for error boundary functionality86export function useErrorBoundary() {87 const [error, setError] = useState<Error | null>(null);8889 const resetError = () => setError(null);9091 const captureError = (error: Error) => {92 setError(error);93 };9495 // Throw error to be caught by nearest error boundary96 if (error) {97 throw error;98 }99100 return { captureError, resetError };101}102103// Usage example104function App() {105 return (106 <ErrorBoundary107 fallback={<CustomErrorFallback />}108 onError={(error, errorInfo) => {109 // Send to monitoring service110 logToService(error, errorInfo);111 }}112 >113 <Header />114 <ErrorBoundary fallback={<ErrorMessage />}>115 <MainContent />116 </ErrorBoundary>117 <Footer />118 </ErrorBoundary>119 );120}
Advanced Example
1// Advanced Error Boundary with recovery2import { Component, ReactNode } from 'react';34interface ErrorBoundaryState {5 hasError: boolean;6 error: Error | null;7 errorCount: number;8 lastErrorTime: number;9}1011interface ErrorBoundaryProps {12 children: ReactNode;13 fallbackComponent?: React.ComponentType<{14 error: Error;15 retry: () => void;16 errorCount: number;17 }>;18 onError?: (error: Error, errorInfo: ErrorInfo, errorCount: number) => void;19 maxRetries?: number;20 resetTimeout?: number;21 isolate?: boolean;22}2324export class AdvancedErrorBoundary extends Component<25 ErrorBoundaryProps,26 ErrorBoundaryState27> {28 private resetTimeoutId: number | null = null;2930 constructor(props: ErrorBoundaryProps) {31 super(props);32 this.state = {33 hasError: false,34 error: null,35 errorCount: 0,36 lastErrorTime: 037 };38 }3940 static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {41 const now = Date.now();42 return {43 hasError: true,44 error,45 lastErrorTime: now46 };47 }4849 componentDidCatch(error: Error, errorInfo: ErrorInfo) {50 const { onError, maxRetries = 3, resetTimeout = 10000 } = this.props;51 const { errorCount, lastErrorTime } = this.state;52 const now = Date.now();5354 // Reset error count if enough time has passed55 const timeSinceLastError = now - lastErrorTime;56 const newErrorCount = timeSinceLastError > resetTimeout ? 1 : errorCount + 1;5758 this.setState({ errorCount: newErrorCount });5960 // Log error61 console.error(`Error Boundary caught error (${newErrorCount}/${maxRetries}):`, error);62 onError?.(error, errorInfo, newErrorCount);6364 // Auto-reset after timeout65 if (this.resetTimeoutId) {66 clearTimeout(this.resetTimeoutId);67 }6869 if (newErrorCount < maxRetries) {70 this.resetTimeoutId = window.setTimeout(() => {71 this.reset();72 }, resetTimeout);73 }74 }7576 reset = () => {77 if (this.resetTimeoutId) {78 clearTimeout(this.resetTimeoutId);79 this.resetTimeoutId = null;80 }8182 this.setState({83 hasError: false,84 error: null,85 errorCount: 0,86 lastErrorTime: 087 });88 };8990 render() {91 const { hasError, error, errorCount } = this.state;92 const { children, fallbackComponent: FallbackComponent, maxRetries = 3, isolate } = this.props;9394 if (hasError && error) {95 // Too many errors - show permanent error state96 if (errorCount >= maxRetries) {97 return (98 <div className="error-boundary-fatal">99 <h2>⚠️ Multiple errors detected</h2>100 <p>This component is experiencing repeated errors.</p>101 <button onClick={() => window.location.reload()}>102 Reload Application103 </button>104 </div>105 );106 }107108 // Custom fallback component109 if (FallbackComponent) {110 return (111 <FallbackComponent112 error={error}113 retry={this.reset}114 errorCount={errorCount}115 />116 );117 }118119 // Default fallback120 return (121 <div className="error-boundary-fallback">122 <h3>Something went wrong</h3>123 <p>{error.message}</p>124 <button onClick={this.reset}>Try Again</button>125 <p className="error-count">126 Error {errorCount} of {maxRetries}127 </p>128 </div>129 );130 }131132 // Isolate errors to this boundary133 if (isolate) {134 return <div className="error-isolation">{children}</div>;135 }136137 return children;138 }139}140141// Async error boundary for Suspense142export function AsyncErrorBoundary({143 children,144 fallback145}: {146 children: ReactNode;147 fallback: ReactNode;148}) {149 return (150 <ErrorBoundary fallback={fallback}>151 <Suspense fallback={<Loading />}>152 {children}153 </Suspense>154 </ErrorBoundary>155 );156}