Skip to content
intermediatePhase 37 · Frontend Architecture

Error Handling Architecture

Design global error boundaries and error recovery strategies.

45m
0 problems
Topic Progress0%

Error Boundaries

Error Boundaries

Error boundaries catch JavaScript errors in their child component tree.

Basic Error Boundary

// components/ErrorBoundary.jsx
import React from 'react';

class ErrorBoundary 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 });
    
    // Log to error reporting service
    console.error('Error Boundary caught:', error, errorInfo);
    
    if (this.props.onError) {
      this.props.onError(error, errorInfo);
    }
  }

  render() {
    if (this.state.hasError) {
      if (this.props.fallback) {
        return this.props.fallback;
      }
      
      return (
        <div className="error-boundary">
          <h2>Something went wrong</h2>
          <details>
            <summary>Error Details</summary>
            <pre>{this.state.error?.message}</pre>
            <pre>{this.state.errorInfo?.componentStack}</pre>
          </details>
          <button onClick={() => window.location.reload()}>
            Reload Page
          </button>
        </div>
      );
    }

    return this.props.children;
  }
}

// Usage
<ErrorBoundary 
  fallback={<CustomErrorScreen />}
  onError={(error, info) => reportError(error, info)}
>
  <App />
</ErrorBoundary>

Nested Error Boundaries

function App() {
  return (
    <ErrorBoundary fallback={<GlobalErrorScreen />}>
      <Header />
      <main>
        <ErrorBoundary fallback={<ContentErrorScreen />}>
          <Content />
        </ErrorBoundary>
      </main>
      <ErrorBoundary fallback={<SidebarErrorScreen />}>
        <Sidebar />
      </ErrorBoundary>
      <Footer />
    </ErrorBoundary>
  );
}

Error Boundary with Recovery

function RecoverableErrorBoundary({ children, maxRetries = 3 }) {
  const [retryCount, setRetryCount] = useState(0);
  const [hasError, setHasError] = useState(false);

  const handleRetry = () => {
    if (retryCount < maxRetries) {
      setRetryCount(prev => prev + 1);
      setHasError(false);
    }
  };

  return (
    <ErrorBoundary
      fallback={
        <div>
          <p>Something went wrong</p>
          {retryCount < maxRetries ? (
            <button onClick={handleRetry}>
              Retry ({maxRetries - retryCount} attempts left)
            </button>
          ) : (
            <button onClick={() => window.location.reload()}>
              Reload Page
            </button>
          )}
        </div>
      }
    >
      {children}
    </ErrorBoundary>
  );
}

Global Error Handling

Global Error Handling

Handle unhandled errors and promise rejections at the application level.

Global Error Handler

// utils/errorHandler.js
export function setupGlobalErrorHandler() {
  // Handle unhandled errors
  window.onerror = (message, source, lineno, colno, error) => {
    reportError({
      type: 'uncaught-error',
      message,
      source,
      lineno,
      colno,
      stack: error?.stack,
    });
    return false;
  };

  // Handle unhandled promise rejections
  window.addEventListener('unhandledrejection', (event) => {
    reportError({
      type: 'unhandled-rejection',
      reason: event.reason?.message || event.reason,
      stack: event.reason?.stack,
    });
    event.preventDefault();
  });
}

// Call early in app initialization
setupGlobalErrorHandler();

Error Toast System

// context/ErrorToastContext.jsx
const ErrorToastContext = createContext(null);

export function ErrorToastProvider({ children }) {
  const [errors, setErrors] = useState([]);

  const showError = (error, options = {}) => {
    const id = Date.now();
    const toast = {
      id,
      message: error.message || 'An error occurred',
      type: options.type || 'error',
      duration: options.duration || 5000,
      action: options.action,
    };
    
    setErrors(prev => [...prev, toast]);
    
    if (toast.duration > 0) {
      setTimeout(() => dismissError(id), toast.duration);
    }
  };

  const dismissError = (id) => {
    setErrors(prev => prev.filter(e => e.id !== id));
  };

  return (
    <ErrorToastContext.Provider value={{ showError, dismissError }}>
      {children}
      <ErrorToastContainer errors={errors} onDismiss={dismissError} />
    </ErrorToastContext.Provider>
  );
}

// Hook
function useErrorToast() {
  const context = useContext(ErrorToastContext);
  if (!context) throw new Error('useErrorToast must be used within ErrorToastProvider');
  return context;
}

// Component
function ErrorToastContainer({ errors, onDismiss }) {
  return (
    <div className="toast-container">
      {errors.map(error => (
        <div key={error.id} className={`toast toast-${error.type}`}>
          <span>{error.message}</span>
          {error.action && (
            <button onClick={error.action.onClick}>
              {error.action.label}
            </button>
          )}
          <button onClick={() => onDismiss(error.id)}>×</button>
        </div>
      ))}
    </div>
  );
}

Error Recovery Patterns

// Automatic retry with exponential backoff
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const response = await fetch(url, options);
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return response;
    } catch (error) {
      if (i === maxRetries - 1) throw error;
      await delay(Math.pow(2, i) * 1000);
    }
  }
}

Error Reporting

Error Reporting

Implement comprehensive error tracking and monitoring.

Error Reporting Service

// services/errorReporting.js
class ErrorReportingService {
  constructor(config) {
    this.dsn = config.dsn;
    this.environment = config.environment;
    this.release = config.release;
    this.user = null;
    this.breadcrumbs = [];
  }

  setUser(user) {
    this.user = user;
  }

  addBreadcrumb(message, category = 'default') {
    this.breadcrumbs.push({
      message,
      category,
      timestamp: new Date().toISOString(),
    });
  }

  captureException(error, context = {}) {
    const payload = {
      exception: {
        type: error.name,
        value: error.message,
        stacktrace: {
          frames: this.parseStack(error.stack),
        },
      },
      timestamp: new Date().toISOString(),
      environment: this.environment,
      release: this.release,
      user: this.user,
      contexts: context,
      breadcrumbs: this.breadcrumbs.slice(-50),
    };

    this.send(payload);
  }

  captureMessage(message, level = 'info') {
    const payload = {
      message,
      level,
      timestamp: new Date().toISOString(),
      environment: this.environment,
      user: this.user,
    };

    this.send(payload);
  }

  parseStack(stack) {
    // Parse stack trace into frames
    return stack?.split('\n').map(line => ({
      filename: line.match(/at (.+?) \\(/)?.[1],
      lineno: parseInt(line.match(/:(\\d+):/)?.[1]),
      function: line.match(/at (.+?)(?: \\()/)?.[1],
    })) || [];
  }

  async send(payload) {
    try {
      await fetch(this.dsn, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
    } catch (e) {
      console.error('Failed to send error report:', e);
    }
  }
}

// Initialize
export const errorReporter = new ErrorReportingService({
  dsn: process.env.ERROR_REPORTING_DSN,
  environment: process.env.NODE_ENV,
  release: process.env.APP_VERSION,
});

React Integration

// hooks/useErrorReporting.js
function useErrorReporting() {
  const { user } = useAuth();

  useEffect(() => {
    errorReporter.setUser(user);
  }, [user]);

  const reportError = useCallback((error, context = {}) => {
    errorReporter.captureException(error, context);
  }, []);

  const reportMessage = useCallback((message, level = 'info') => {
    errorReporter.captureMessage(message, level);
  }, []);

  const addBreadcrumb = useCallback((message, category) => {
    errorReporter.addBreadcrumb(message, category);
  }, []);

  return { reportError, reportMessage, addBreadcrumb };
}

// Usage in components
function CheckoutPage() {
  const { reportError, addBreadcrumb } = useErrorReporting();

  const handlePayment = async () => {
    addBreadcrumb('Payment initiated', 'payment');
    try {
      await processPayment();
    } catch (error) {
      reportError(error, { page: 'checkout', step: 'payment' });
      showError('Payment failed. Please try again.');
    }
  };
}

Practice Problems

0/3solved
Build Error Handling Architecture Component

Create a reusable React component implementing Error Handling Architecture. Include proper state management and accessibility.

Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where needed
Error Handling Architecture Testing

Write unit and integration tests for Error Handling Architecture using React Testing Library.

Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility tests
Error Handling Architecture Performance

Optimize Error Handling Architecture for performance. Consider memoization, code splitting, and bundle size.

Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysis

Quiz

1. What can error boundaries catch?

Question 1 options

2. Why should error reporting happen in componentDidCatch?

Question 2 options

3. What should global error handlers capture?

Question 3 options

4. What information should error reports include?

Question 4 options

Flashcards

Question

What is an error boundary?

Answer

A React component that catches JavaScript errors in its child component tree and displays a fallback UI.

Question

What should global error handlers capture?

Answer

Uncaught errors (window.onerror) and unhandled promise rejections (unhandledrejection event).

Question

What are breadcrumbs in error reporting?

Answer

A log of user actions leading up to an error, helping debug the sequence of events.

Question

Why use nested error boundaries?

Answer

Question

What is Error Handling Architecture?

Answer

Error Handling Architecture is a key concept in frontend development.

Revision Notes

Key Takeaways

  • 1.Error boundaries catch errors during rendering in the component tree
  • 2.Use nested error boundaries to isolate errors to specific sections
  • 3.Set up global handlers for uncaught errors and promise rejections
  • 4.Error reports should include stack traces, context, and breadcrumbs
  • 5.Implement retry mechanisms with exponential backoff

Interview Tips

  • Explain what error boundaries can and cannot catch
  • Describe a comprehensive error handling strategy
  • Know the difference between error boundaries and try-catch

Cheat Sheet

Error Handling Architecture Cheat Sheet

Error Boundary

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  componentDidCatch(error, info) {
    logError(error, info);
  }
}

Global Handlers

window.onerror = (msg, src, line, col, error) => { ... };
window.addEventListener('unhandledrejection', (e) => { ... });

Error Reporting

  • Include stack trace
  • Add user context
  • Track breadcrumbs
  • Set environment info