Resourcesโ€บReact Patternsโ€บReact Error Boundaries: Catch UI Errors Before They Crash the App
โš›๏ธReact Patternsโ€” React Error Boundaries: Catch UI Errors Before They Crash the Appโฑ 6 min

React Error Boundaries: Catch UI Errors Before They Crash the App

Error boundaries let you catch JavaScript errors in component trees, show fallback UI, and keep the rest of your app working.

๐Ÿ“…February 7, 2026โœTechTwitter.ioreacterror-boundarieserror-handlingpatterns

The Problem

Without error boundaries, a JavaScript error in any component crashes your entire React app and shows a blank screen. Users have no idea what happened.

TypeError: Cannot read properties of null (reading 'name')
    at UserCard (UserCard.tsx:12)
    at ...

The entire app unmounts. Every user on that page sees a blank white screen.


What Error Boundaries Do

An error boundary is a React component that:

  1. Catches JavaScript errors from its child component tree
  2. Renders a fallback UI instead of the crashed component
  3. Lets the rest of the app continue working

It's like a try/catch, but for React component rendering.


Creating an Error Boundary

Error boundaries must be class components (as of React 18 โ€” useErrorBoundary hook is still experimental in most setups):

import { Component, type ReactNode } from 'react'

interface Props {
  children: ReactNode
  fallback?: ReactNode
  onError?: (error: Error, info: { componentStack: string }) => void
}

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

export class ErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props)
    this.state = { hasError: false, error: null }
  }

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

  componentDidCatch(error: Error, info: { componentStack: string }) {
    // Log to your error tracking service
    this.props.onError?.(error, info)
    console.error('Error caught by boundary:', error, info)
  }

  render() {
    if (this.state.hasError) {
      return this.props.fallback ?? (
        <div className="error-fallback">
          <h2>Something went wrong</h2>
          <p>{this.state.error?.message}</p>
          <button onClick={() => this.setState({ hasError: false, error: null })}>
            Try again
          </button>
        </div>
      )
    }
    return this.props.children
  }
}

Using Error Boundaries

Protect the entire app

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ErrorBoundary fallback={<AppErrorPage />}>
          {children}
        </ErrorBoundary>
      </body>
    </html>
  )
}

Protect individual sections

Better: use multiple granular boundaries so one broken widget doesn't take down the page:

export default function Dashboard() {
  return (
    <div>
      <ErrorBoundary fallback={<WidgetError name="Analytics" />}>
        <AnalyticsWidget />
      </ErrorBoundary>

      <ErrorBoundary fallback={<WidgetError name="Recent Activity" />}>
        <ActivityFeed />
      </ErrorBoundary>

      <ErrorBoundary fallback={<WidgetError name="User Stats" />}>
        <UserStats />
      </ErrorBoundary>
    </div>
  )
}

If AnalyticsWidget throws, only that widget shows an error. The rest of the dashboard continues working.


Next.js Error Boundaries: error.tsx

In Next.js App Router, create error.tsx next to page.tsx โ€” it automatically becomes the error boundary for that route segment:

// app/dashboard/error.tsx
'use client'  // Error boundaries must be client components

export default function DashboardError({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  return (
    <div>
      <h2>Dashboard failed to load</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Retry</button>
    </div>
  )
}

The reset function re-renders the route segment โ€” no full page reload needed.


What Error Boundaries Don't Catch

Error boundaries only catch errors during rendering and lifecycle methods. They don't catch:

// โŒ Not caught: async errors in event handlers
<button onClick={async () => {
  throw new Error('not caught by boundary')
}}>

// โŒ Not caught: errors in useEffect
useEffect(() => {
  throw new Error('not caught by boundary')
}, [])

// โŒ Not caught: errors in async components (need server error.tsx)

// โœ… Caught: errors during render
function BrokenComponent() {
  throw new Error('caught!')
  return <div />
}

For async errors, use your own try/catch + error state, or a library like react-query which surfaces async errors in a form error boundaries can catch.


Integrating with Error Monitoring

import * as Sentry from '@sentry/react'

// Use Sentry's pre-built error boundary
<Sentry.ErrorBoundary
  fallback={({ error, resetError }) => (
    <ErrorPage error={error} onReset={resetError} />
  )}
  showDialog  // Opens Sentry's user feedback dialog
>
  <App />
</Sentry.ErrorBoundary>

Key Takeaways

  • Error boundaries catch render errors and prevent the entire app from crashing
  • Use multiple granular boundaries โ€” one per section/widget, not just one at the root
  • In Next.js: create error.tsx next to page.tsx for route-level error boundaries
  • Error boundaries don't catch async errors, event handler errors, or useEffect errors
  • Always connect to error monitoring (Sentry, Datadog) in componentDidCatch