Error Boundaries

Med

Error boundaries are React components that catch JavaScript errors in their child component tree during rendering, in lifecycle methods, and in constructors, then display a fallback UI. They prevent a single component crash from taking down the entire application.

Interactive Visualization

Key Points

  • Implemented as class components using getDerivedStateFromError and componentDidCatch
  • Catch errors during rendering, lifecycle methods, and constructors
  • Do NOT catch errors in event handlers, async code, or SSR
  • Place boundaries strategically: route-level, feature-level, or widget-level
  • componentDidCatch receives error info including the component stack trace
  • Cannot be implemented as functional components (no hook equivalent yet)

Code Examples

Error Boundary Component

import { Component, type ReactNode, type ErrorInfo } from 'react'

interface ErrorBoundaryProps {
  children: ReactNode
  fallback: ReactNode
  onError?: (error: Error, info: ErrorInfo) => void
}

interface ErrorBoundaryState {
  hasError: boolean
  error: Error | null
}

class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
  state: ErrorBoundaryState = { hasError: false, error: null }

  static getDerivedStateFromError(error: Error): ErrorBoundaryState {
    return { hasError: true, error }
  }

  componentDidCatch(error: Error, info: ErrorInfo): void {
    this.props.onError?.(error, info)
  }

  render() {
    if (this.state.hasError) return this.props.fallback
    return this.props.children
  }
}

// Usage: isolate crash zones
function App() {
  return (
    <ErrorBoundary fallback={<p>App crashed. Please refresh.</p>}>
      <Header />
      <ErrorBoundary fallback={<p>Widget failed to load.</p>}>
        <UnstableWidget />
      </ErrorBoundary>
      <Footer />
    </ErrorBoundary>
  )
}

Nested boundaries let you isolate failures: a widget crash shows a local fallback without taking down the header or footer

Resettable Error Boundary

import { Component, type ReactNode, type ErrorInfo } from 'react'

interface ResettableErrorBoundaryProps {
  children: ReactNode
  resetKey: string | number
}

interface ResettableErrorBoundaryState {
  hasError: boolean
}

class ResettableErrorBoundary extends Component<
  ResettableErrorBoundaryProps,
  ResettableErrorBoundaryState
> {
  state: ResettableErrorBoundaryState = { hasError: false }

  static getDerivedStateFromError(): ResettableErrorBoundaryState {
    return { hasError: true }
  }

  componentDidUpdate(prevProps: ResettableErrorBoundaryProps): void {
    if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) {
      this.setState({ hasError: false })
    }
  }

  componentDidCatch(error: Error, info: ErrorInfo): void {
    // Log to error reporting service
  }

  render() {
    if (this.state.hasError) {
      return <button onClick={() => this.setState({ hasError: false })}>Retry</button>
    }
    return this.props.children
  }
}

A resetKey prop lets the boundary recover when data changes, and a retry button lets users attempt recovery manually

Common Mistakes

  • Expecting error boundaries to catch errors in event handlers or async code
  • Placing a single boundary at the root instead of granular boundaries around risky features
  • Not logging errors to an external service in componentDidCatch

Interview Tips

  • Know exactly what error boundaries catch and what they do not
  • Explain the strategy of multiple boundaries at different granularity levels
  • Discuss how to handle errors in event handlers with try-catch and local state

Related Concepts