Error Handling and Error Boundaries

1. Error Boundary Components and Implementation

Concept Description Catches Does NOT Catch
Error Boundary Class component that catches errors in child tree Render errors, lifecycle errors Event handlers, async, SSR
Placement Wrap components that might error All descendants Errors in boundary itself
Granularity Multiple boundaries for isolation Specific sections only Unrelated components
Error Type Caught by Boundary? Handling Approach
Render errors ✅ Yes Error boundary
Lifecycle methods ✅ Yes Error boundary
Event handlers ❌ No try/catch in handler
Async code (setTimeout) ❌ No try/catch in callback
Server-side rendering ❌ No Server error handling
Error in boundary itself ❌ No Parent error boundary

Example: Error boundary implementation

// Basic error boundary class component
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    // Update state so next render shows fallback UI
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // Log error to reporting service
    console.error('Error caught by boundary:', error, errorInfo);
    // logErrorToService(error, errorInfo);
  }
  
  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h1>Something went wrong.</h1>
          <details>
            <summary>Error details</summary>
            <pre>{this.state.error.toString()}</pre>
          </details>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Usage - wrap components
const App = () => (
  <ErrorBoundary>
    <Header />
    <Main />
    <Footer />
  </ErrorBoundary>
);

// Multiple boundaries for isolation
const Dashboard = () => (
  <div>
    <ErrorBoundary>
      <Sidebar />
    </ErrorBoundary>
    
    <ErrorBoundary>
      <MainContent />
    </ErrorBoundary>
    
    <ErrorBoundary>
      <Widgets />
    </ErrorBoundary>
  </div>
);

// Enhanced error boundary with retry
class ErrorBoundaryWithRetry extends React.Component {
  constructor(props) {
    super(props);
    this.state = { 
      hasError: false, 
      error: null,
      errorInfo: null
    };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    this.setState({ errorInfo });
    console.error('Error:', error);
    console.error('Component stack:', errorInfo.componentStack);
  }
  
  handleReset = () => {
    this.setState({ 
      hasError: false, 
      error: null,
      errorInfo: null
    });
  };
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-container">
          <h2>Oops! Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={this.handleReset}>
            Try Again
          </button>
          {process.env.NODE_ENV === 'development' && (
            <details>
              <summary>Stack trace</summary>
              <pre>{this.state.errorInfo?.componentStack}</pre>
            </details>
          )}
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Custom fallback component
const ErrorFallback = ({ error, resetError }) => (
  <div role="alert">
    <h2>Something went wrong</h2>
    <pre style={{ color: 'red' }}>{error.message}</pre>
    <button onClick={resetError}>Try again</button>
  </div>
);

class ErrorBoundaryWithFallback extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    this.props.onError?.(error, errorInfo);
  }
  
  resetError = () => {
    this.setState({ hasError: false, error: null });
  };
  
  render() {
    if (this.state.hasError) {
      const FallbackComponent = this.props.fallback || ErrorFallback;
      return (
        <FallbackComponent 
          error={this.state.error}
          resetError={this.resetError}
        />
      );
    }
    
    return this.props.children;
  }
}

// Usage with custom fallback
const App = () => (
  <ErrorBoundaryWithFallback
    fallback={CustomErrorPage}
    onError={(error, info) => logToService(error, info)}
  >
    <Routes />
  </ErrorBoundaryWithFallback>
);

// Component that will error
const BuggyComponent = () => {
  const [count, setCount] = useState(0);
  
  if (count > 3) {
    throw new Error('Count exceeded limit!');
  }
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
};
Warning: Error boundaries do NOT catch errors in event handlers, async code (setTimeout, promises), server-side rendering, or errors thrown in the error boundary itself. Use try/catch for those cases.

2. componentDidCatch and getDerivedStateFromError

Method When Called Purpose Can Update State
getDerivedStateFromError During render phase Update state to show fallback UI ✅ Yes (return new state)
componentDidCatch After commit phase Log errors, report to service ⚠️ Yes but triggers extra render
Parameter Available In Contains
error Both methods Error object with message, stack
errorInfo componentDidCatch only componentStack - where error occurred

Example: Using both lifecycle methods

class AdvancedErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hasError: false,
      error: null,
      errorInfo: null,
      errorCount: 0
    };
  }
  
  // Static method - called during render phase
  // MUST be pure - no side effects!
  static getDerivedStateFromError(error) {
    // Update state to trigger fallback UI
    return {
      hasError: true,
      error: error
    };
  }
  
  // Called after render in commit phase
  // Can have side effects - logging, reporting
  componentDidCatch(error, errorInfo) {
    // errorInfo.componentStack shows component hierarchy
    console.error('Error caught:', error);
    console.error('Component stack:', errorInfo.componentStack);
    
    // Update state to store error details
    this.setState(prevState => ({
      errorInfo,
      errorCount: prevState.errorCount + 1
    }));
    
    // Report to error tracking service
    if (this.props.onError) {
      this.props.onError(error, errorInfo);
    }
    
    // Send to monitoring service
    // Sentry.captureException(error, { contexts: { react: errorInfo } });
  }
  
  componentDidUpdate(prevProps, prevState) {
    // Clear error if children change (new route, etc)
    if (this.state.hasError && prevProps.children !== this.props.children) {
      this.setState({ hasError: false, error: null, errorInfo: null });
    }
  }
  
  handleReset = () => {
    this.setState({
      hasError: false,
      error: null,
      errorInfo: null
    });
  };
  
  render() {
    if (this.state.hasError) {
      return (
        <div className="error-boundary">
          <h1>Something went wrong</h1>
          <p>{this.state.error?.message}</p>
          <p>Error count: {this.state.errorCount}</p>
          
          {process.env.NODE_ENV === 'development' && (
            <>
              <h3>Error Stack:</h3>
              <pre>{this.state.error?.stack}</pre>
              
              <h3>Component Stack:</h3>
              <pre>{this.state.errorInfo?.componentStack}</pre>
            </>
          )}
          
          <button onClick={this.handleReset}>
            Try Again
          </button>
        </div>
      );
    }
    
    return this.props.children;
  }
}

// Error boundary with different fallbacks based on error type
class SmartErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false, error: null };
  }
  
  static getDerivedStateFromError(error) {
    return { hasError: true, error };
  }
  
  componentDidCatch(error, errorInfo) {
    // Categorize errors
    const errorType = this.categorizeError(error);
    
    console.error(\`[\${errorType}] Error:`, error);
    console.error('Stack:', errorInfo.componentStack);
    
    // Report with category
    this.reportError(error, errorInfo, errorType);
  }
  
  categorizeError(error) {
    if (error.message.includes('fetch')) return 'NETWORK';
    if (error.message.includes('undefined')) return 'RUNTIME';
    if (error.name === 'ChunkLoadError') return 'CHUNK_LOAD';
    return 'UNKNOWN';
  }
  
  reportError(error, errorInfo, category) {
    // Send to monitoring service
    fetch('/api/errors', {
      method: 'POST',
      body: JSON.stringify({
        message: error.message,
        stack: error.stack,
        componentStack: errorInfo.componentStack,
        category,
        timestamp: Date.now(),
        userAgent: navigator.userAgent
      })
    });
  }
  
  render() {
    if (this.state.hasError) {
      const errorType = this.categorizeError(this.state.error);
      
      // Different UI based on error type
      switch (errorType) {
        case 'NETWORK':
          return <NetworkErrorFallback />;
        case 'CHUNK_LOAD':
          return <ChunkLoadErrorFallback />;
        default:
          return <GenericErrorFallback error={this.state.error} />;
      }
    }
    
    return this.props.children;
  }
}
Note: Use getDerivedStateFromError for UI updates (pure), and componentDidCatch for side effects like logging. getDerivedStateFromError is called during render, so it must be pure.

3. useErrorHandler Hook Patterns

Pattern Description Use Case
Custom hook Throw errors to boundary from hooks Event handlers, async operations
Error state Store error in state, throw in render Propagate to error boundary
Library (react-error-boundary) Pre-built hooks and components Production-ready solution

Example: useErrorHandler custom hook

// Custom useErrorHandler hook
const useErrorHandler = () => {
  const [error, setError] = useState(null);
  
  // Throw error during render to trigger error boundary
  if (error) {
    throw error;
  }
  
  return setError;
};

// Usage in event handlers
const MyComponent = () => {
  const handleError = useErrorHandler();
  
  const handleClick = async () => {
    try {
      await somethingThatMightFail();
    } catch (error) {
      handleError(error); // Propagates to error boundary
    }
  };
  
  return <button onClick={handleClick}>Click me</button>;
};

// Using with data fetching
const UserProfile = ({ userId }) => {
  const [user, setUser] = useState(null);
  const handleError = useErrorHandler();
  
  useEffect(() => {
    const fetchUser = async () => {
      try {
        const response = await fetch(\`/api/users/\${userId}\`);
        if (!response.ok) throw new Error('Failed to fetch user');
        const data = await response.json();
        setUser(data);
      } catch (error) {
        handleError(error); // Triggers error boundary
      }
    };
    
    fetchUser();
  }, [userId, handleError]);
  
  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
};

// Using react-error-boundary library
import { useErrorHandler, ErrorBoundary } from 'react-error-boundary';

const DataFetcher = () => {
  const handleError = useErrorHandler();
  
  useEffect(() => {
    fetchData()
      .catch(handleError); // Automatically propagates to boundary
  }, [handleError]);
  
  return <div>Data</div>;
};

// Error boundary with hook from library
const App = () => (
  <ErrorBoundary
    FallbackComponent={ErrorFallback}
    onError={(error, errorInfo) => {
      console.error('Error:', error);
      console.error('Info:', errorInfo);
    }}
    onReset={() => {
      // Reset app state
      window.location.href = '/';
    }}
  >
    <DataFetcher />
  </ErrorBoundary>
);

// Advanced hook with error context
const ErrorContext = createContext();

const ErrorProvider = ({ children }) => {
  const [errors, setErrors] = useState([]);
  
  const addError = useCallback((error) => {
    setErrors(prev => [...prev, {
      id: Date.now(),
      message: error.message,
      timestamp: new Date()
    }]);
  }, []);
  
  const clearErrors = useCallback(() => {
    setErrors([]);
  }, []);
  
  return (
    <ErrorContext.Provider value={{ errors, addError, clearErrors }}>
      {children}
    </ErrorContext.Provider>
  );
};

const useError = () => {
  const context = useContext(ErrorContext);
  if (!context) {
    throw new Error('useError must be used within ErrorProvider');
  }
  return context;
};

// Usage
const MyComponent = () => {
  const { addError, errors } = useError();
  
  const handleAction = async () => {
    try {
      await riskyOperation();
    } catch (error) {
      addError(error);
    }
  };
  
  return (
    <div>
      {errors.map(err => (
        <div key={err.id} className="error-toast">
          {err.message}
        </div>
      ))}
      <button onClick={handleAction}>Action</button>
    </div>
  );
};

// Retrying with hook
const useAsyncError = () => {
  const [, setError] = useState();
  
  return useCallback(
    (error) => {
      setError(() => {
        throw error;
      });
    },
    []
  );
};

const ComponentWithRetry = () => {
  const throwError = useAsyncError();
  const [retryCount, setRetryCount] = useState(0);
  
  const fetchData = async () => {
    try {
      const data = await fetch('/api/data');
      if (!data.ok) throw new Error('Fetch failed');
      return data.json();
    } catch (error) {
      if (retryCount < 3) {
        setRetryCount(retryCount + 1);
      } else {
        throwError(error);
      }
    }
  };
  
  useEffect(() => {
    fetchData();
  }, [retryCount]);
  
  return <div>Content</div>;
};
Note: Consider using the react-error-boundary library for production apps. It provides battle-tested hooks and components with advanced features like retry, reset keys, and more.

4. Async Error Handling and Promise Rejections

Async Scenario Error Boundary? Solution
useEffect fetch ❌ No try/catch + error state or hook
Event handler async ❌ No try/catch + error state
setTimeout/setInterval ❌ No try/catch in callback
Promise.catch ❌ No .catch() or try/catch with await
Unhandled rejection ❌ No Global handler + error boundary

Example: Async error handling patterns

// Async/await with try/catch
const DataComponent = () => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        setError(null);
        
        const response = await fetch('/api/data');
        
        if (!response.ok) {
          throw new Error(\`HTTP error! status: \${response.status}\`);
        }
        
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err.message);
        console.error('Fetch error:', err);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, []);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  return <div>{JSON.stringify(data)}</div>;
};

// Promise chains with .catch()
const PromiseChainComponent = () => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch('/api/data')
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        return response.json();
      })
      .then(data => setData(data))
      .catch(error => {
        console.error('Error:', error);
        setError(error.message);
      });
  }, []);
  
  if (error) return <div>Error: {error}</div>;
  return <div>{JSON.stringify(data)}</div>;
};

// Global unhandled rejection handler
const setupGlobalErrorHandling = () => {
  // Unhandled promise rejections
  window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled promise rejection:', event.reason);
    
    // Prevent default browser handling
    event.preventDefault();
    
    // Report to monitoring service
    reportError({
      type: 'unhandled_rejection',
      error: event.reason,
      promise: event.promise
    });
  });
  
  // Global error handler
  window.addEventListener('error', (event) => {
    console.error('Global error:', event.error);
    
    reportError({
      type: 'global_error',
      error: event.error,
      message: event.message,
      filename: event.filename,
      lineno: event.lineno,
      colno: event.colno
    });
  });
};

// Call in app initialization
setupGlobalErrorHandling();

// Async error with custom hook
const useAsyncOperation = (asyncFn) => {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  const [loading, setLoading] = useState(false);
  
  const execute = useCallback(async (...args) => {
    try {
      setLoading(true);
      setError(null);
      const result = await asyncFn(...args);
      setData(result);
      return result;
    } catch (err) {
      setError(err);
      throw err; // Re-throw if caller wants to handle
    } finally {
      setLoading(false);
    }
  }, [asyncFn]);
  
  return { data, error, loading, execute };
};

// Usage
const MyComponent = () => {
  const { data, error, loading, execute } = useAsyncOperation(
    async (id) => {
      const response = await fetch(\`/api/users/\${id}\`);
      if (!response.ok) throw new Error('Fetch failed');
      return response.json();
    }
  );
  
  const handleClick = async () => {
    try {
      await execute(123);
    } catch (err) {
      console.error('Operation failed:', err);
    }
  };
  
  return (
    <div>
      <button onClick={handleClick} disabled={loading}>
        Fetch User
      </button>
      {loading && <div>Loading...</div>}
      {error && <div>Error: {error.message}</div>}
      {data && <div>{data.name}</div>}
    </div>
  );
};

// Timeout with error handling
const ComponentWithTimeout = () => {
  const [message, setMessage] = useState('');
  const handleError = useErrorHandler();
  
  useEffect(() => {
    const timerId = setTimeout(() => {
      try {
        // Risky operation
        riskyOperation();
        setMessage('Success');
      } catch (error) {
        handleError(error);
      }
    }, 1000);
    
    return () => clearTimeout(timerId);
  }, [handleError]);
  
  return <div>{message}</div>;
};

// Parallel requests with error handling
const MultipleRequests = () => {
  const [data, setData] = useState(null);
  const [errors, setErrors] = useState([]);
  
  useEffect(() => {
    const fetchAll = async () => {
      const urls = ['/api/data1', '/api/data2', '/api/data3'];
      
      // Promise.allSettled to handle partial failures
      const results = await Promise.allSettled(
        urls.map(url => fetch(url).then(r => r.json()))
      );
      
      const successful = results
        .filter(r => r.status === 'fulfilled')
        .map(r => r.value);
      
      const failed = results
        .filter(r => r.status === 'rejected')
        .map(r => r.reason);
      
      setData(successful);
      setErrors(failed);
    };
    
    fetchAll();
  }, []);
  
  return (
    <div>
      {errors.length > 0 && (
        <div>
          {errors.length} requests failed
        </div>
      )}
      {data && <div>{JSON.stringify(data)}</div>}
    </div>
  );
};
Warning: Always handle promise rejections! Unhandled rejections can cause silent failures. Use try/catch with async/await or .catch() with promises. Set up global handlers as a safety net.

5. Error Reporting and Monitoring Integration

Service Features Integration
Sentry Error tracking, performance, releases @sentry/react SDK
LogRocket Session replay, error tracking logrocket + logrocket-react
Bugsnag Error monitoring, releases @bugsnag/js + @bugsnag/plugin-react
Rollbar Real-time error tracking rollbar package

Example: Error monitoring integration

// Sentry integration
import * as Sentry from '@sentry/react';

// Initialize Sentry
Sentry.init({
  dsn: 'YOUR_SENTRY_DSN',
  environment: process.env.NODE_ENV,
  release: process.env.REACT_APP_VERSION,
  tracesSampleRate: 1.0, // Capture 100% of transactions
  integrations: [
    new Sentry.BrowserTracing(),
    new Sentry.Replay()
  ],
  beforeSend(event, hint) {
    // Modify or filter events before sending
    if (event.exception) {
      console.error('Sending error to Sentry:', hint.originalException);
    }
    return event;
  }
});

// Use Sentry's error boundary
const App = () => (
  <Sentry.ErrorBoundary
    fallback={({ error, resetError }) => (
      <div>
        <h1>An error occurred</h1>
        <p>{error.message}</p>
        <button onClick={resetError}>Try again</button>
      </div>
    )}
    showDialog
  >
    <Routes />
  </Sentry.ErrorBoundary>
);

// Manual error capture
const handleError = (error) => {
  Sentry.captureException(error, {
    tags: {
      component: 'UserProfile',
      action: 'fetchUser'
    },
    level: 'error',
    extra: {
      userId: user.id,
      timestamp: Date.now()
    }
  });
};

// Add user context
Sentry.setUser({
  id: user.id,
  email: user.email,
  username: user.username
});

// Add breadcrumbs for debugging
Sentry.addBreadcrumb({
  category: 'user-action',
  message: 'User clicked submit button',
  level: 'info'
});

// LogRocket integration
import LogRocket from 'logrocket';
import setupLogRocketReact from 'logrocket-react';

// Initialize LogRocket
LogRocket.init('your-app-id/project-name', {
  release: process.env.REACT_APP_VERSION,
  console: {
    shouldAggregateConsoleErrors: true
  }
});

setupLogRocketReact(LogRocket);

// Identify users
LogRocket.identify(user.id, {
  name: user.name,
  email: user.email,
  subscriptionType: user.plan
});

// LogRocket with Sentry
LogRocket.getSessionURL((sessionURL) => {
  Sentry.configureScope((scope) => {
    scope.setExtra('sessionURL', sessionURL);
  });
});

// Custom error reporting service
class ErrorReporter {
  static async report(error, errorInfo, context = {}) {
    const errorData = {
      message: error.message,
      stack: error.stack,
      componentStack: errorInfo?.componentStack,
      context,
      userAgent: navigator.userAgent,
      url: window.location.href,
      timestamp: new Date().toISOString(),
      user: this.getUserContext()
    };
    
    try {
      await fetch('/api/errors', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(errorData)
      });
    } catch (reportError) {
      console.error('Failed to report error:', reportError);
    }
  }
  
  static getUserContext() {
    // Get user info from your auth system
    return {
      id: localStorage.getItem('userId'),
      sessionId: localStorage.getItem('sessionId')
    };
  }
}

// Use in error boundary
class MonitoredErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  componentDidCatch(error, errorInfo) {
    // Report to multiple services
    ErrorReporter.report(error, errorInfo, {
      component: this.props.name
    });
    
    Sentry.captureException(error, {
      contexts: { react: errorInfo }
    });
    
    LogRocket.captureException(error, {
      extra: errorInfo
    });
  }
  
  render() {
    if (this.state.hasError) {
      return <ErrorFallback />;
    }
    return this.props.children;
  }
}

// Automatic error reporting with fetch interceptor
const originalFetch = window.fetch;
window.fetch = async (...args) => {
  try {
    const response = await originalFetch(...args);
    
    if (!response.ok) {
      Sentry.captureMessage(\`HTTP Error: \${response.status}\`, {
        level: 'warning',
        extra: {
          url: args[0],
          status: response.status
        }
      });
    }
    
    return response;
  } catch (error) {
    Sentry.captureException(error, {
      tags: { type: 'fetch-error' }
    });
    throw error;
  }
};
Note: Choose an error monitoring service that fits your needs. Sentry is popular for error tracking, LogRocket adds session replay, and both provide valuable debugging context. Always sanitize sensitive data before reporting.

6. Fallback UI Components and Recovery Patterns

Pattern Description User Experience
Full page fallback Replace entire app with error page Critical errors only
Section fallback Replace section with error message Rest of app still works
Inline error Show error inline in component Minimal disruption
Retry button Let user retry failed operation Self-service recovery
Automatic retry Retry with exponential backoff Seamless recovery attempt
Graceful degradation Show partial content/reduced features Better than nothing

Example: Fallback UI and recovery patterns

// Simple error fallback
const ErrorFallback = ({ error, resetError }) => (
  <div className="error-container">
    <h2>⚠️ Something went wrong</h2>
    <p>{error.message}</p>
    <button onClick={resetError}>Try again</button>
  </div>
);

// Feature-specific fallback
const FeatureFallback = ({ error, resetError, featureName }) => (
  <div className="feature-error">
    <p>Unable to load {featureName}</p>
    <button onClick={resetError}>Retry</button>
    <button onClick={() => window.location.reload()}>
      Reload Page
    </button>
  </div>
);

// Graceful degradation example
const DataList = ({ items }) => {
  const [error, setError] = useState(null);
  const [enhancedData, setEnhancedData] = useState(null);
  
  useEffect(() => {
    // Try to enhance data with additional info
    const enhance = async () => {
      try {
        const enhanced = await fetchEnhancedData(items);
        setEnhancedData(enhanced);
      } catch (err) {
        setError(err);
        // Continue with basic data
      }
    };
    
    enhance();
  }, [items]);
  
  // Show basic data if enhancement fails
  const displayData = enhancedData || items;
  
  return (
    <div>
      {error && (
        <div className="warning">
          Showing basic view (enhanced features unavailable)
        </div>
      )}
      {displayData.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
};

// Error boundary with reset key
const App = () => {
  const [resetKey, setResetKey] = useState(0);
  
  return (
    <ErrorBoundary
      resetKey={resetKey} // Changes when state updates
      onReset={() => setResetKey(k => k + 1)}
      FallbackComponent={ErrorFallback}
    >
      <Routes />
    </ErrorBoundary>
  );
};

// Automatic retry with exponential backoff
const useRetryableAsync = (asyncFn, maxRetries = 3) => {
  const [state, setState] = useState({
    data: null,
    error: null,
    loading: false,
    retryCount: 0
  });
  
  const execute = useCallback(async (...args) => {
    setState(s => ({ ...s, loading: true, error: null }));
    
    for (let i = 0; i <= maxRetries; i++) {
      try {
        const result = await asyncFn(...args);
        setState({
          data: result,
          error: null,
          loading: false,
          retryCount: i
        });
        return result;
      } catch (error) {
        if (i === maxRetries) {
          setState({
            data: null,
            error,
            loading: false,
            retryCount: i
          });
          throw error;
        }
        
        // Exponential backoff: 1s, 2s, 4s
        const delay = Math.pow(2, i) * 1000;
        await new Promise(resolve => setTimeout(resolve, delay));
      }
    }
  }, [asyncFn, maxRetries]);
  
  return { ...state, execute };
};

// Usage
const DataComponent = () => {
  const { data, error, loading, retryCount, execute } = useRetryableAsync(
    async () => {
      const response = await fetch('/api/data');
      if (!response.ok) throw new Error('Fetch failed');
      return response.json();
    }
  );
  
  useEffect(() => {
    execute();
  }, [execute]);
  
  if (loading) return <div>Loading... (attempt {retryCount + 1}/3)</div>;
  if (error) return <div>Failed after {retryCount} retries</div>;
  return <div>{JSON.stringify(data)}</div>;
};

// Chunk load error recovery (code splitting)
const ChunkLoadErrorFallback = () => (
  <div className="chunk-error">
    <h2>Update Available</h2>
    <p>A new version of the app is available.</p>
    <button onClick={() => window.location.reload()}>
      Reload to Update
    </button>
  </div>
);

// Network error fallback
const NetworkErrorFallback = ({ resetError }) => (
  <div className="network-error">
    <h2>🌐 Connection Issue</h2>
    <p>Please check your internet connection.</p>
    <button onClick={resetError}>Retry</button>
  </div>
);

// Multiple error boundaries for granular fallbacks
const Dashboard = () => (
  <div>
    <Header /> {/* Outside boundaries - always shown */}
    
    <div className="dashboard-content">
      <ErrorBoundary FallbackComponent={SidebarError}>
        <Sidebar />
      </ErrorBoundary>
      
      <ErrorBoundary FallbackComponent={MainContentError}>
        <MainContent />
      </ErrorBoundary>
      
      <ErrorBoundary FallbackComponent={WidgetError}>
        <Widgets />
      </ErrorBoundary>
    </div>
  </div>
);

Error Handling Best Practices