Home/Learn/React/Error Boundaries

Error Boundaries

Intermediate
Patterns

Without a boundary, one thrown error unmounts your entire application and leaves a blank white page. A boundary contains the damage to one part of the screen.

Overview

React deliberately unmounts the whole tree when a render throws, on the grounds that a half-rendered UI can be worse than none — think a banking screen showing the wrong balance. That is only a safe default if you place boundaries, and most apps discover this the first time a null slips into a component and a user reports a completely white page. A boundary catches errors from the components beneath it and renders a fallback instead. Placement is the skill: too high and everything still disappears, too low and every widget needs one.

Writing One

Still a class component — the two lifecycle methods have no hook equivalent.

getDerivedStateFromError and componentDidCatch
class ErrorBoundary extends Component {
  state = { error: null }

  static getDerivedStateFromError(error) {
    return { error }                       // render the fallback
  }

  componentDidCatch(error, info) {
    reportError(error, { componentStack: info.componentStack })  // log it
  }

  render() {
    if (this.state.error) {
      return this.props.fallback?.(this.state.error, () => this.setState({ error: null }))
        ?? <DefaultErrorFallback />
    }
    return this.props.children
  }
}

// react-error-boundary wraps this with a friendlier API, including
// a reset that also resets the subtree:
<ErrorBoundary
  FallbackComponent={ErrorFallback}
  onReset={() => queryClient.resetQueries()}
  resetKeys={[location.pathname]}          // auto-reset on navigation
>

What It Does Not Catch

The list matters, because the gaps are where the blank pages still come from.

Render errors only — handlers and promises need their own catch
// A boundary catches errors thrown during RENDER, in lifecycle
// methods, and in constructors of the tree below it.

// It does NOT catch:
//   - event handlers          -> try/catch in the handler
//   - async code / promises   -> .catch, or a query library's error state
//   - setTimeout callbacks
//   - server-side rendering
//   - errors thrown in the boundary itself

async function handleSubmit() {
  try { await save() }
  catch (e) { setError(e) }        // the boundary will never see this
}

// A rejected promise with no handler does not reach a boundary
// either. Catch globally so it is at least reported:
window.addEventListener('unhandledrejection', e => reportError(e.reason))

// Query libraries can rethrow into a boundary if you prefer one
// error path: useQuery({ throwOnError: true })

Where to Put Them

Layered, so a failure costs the smallest possible piece of the screen.

App, route, and widget layers
// 1. Around the whole app — the last resort, so a crash still
//    renders something with a reload option
<ErrorBoundary fallback={<AppCrashed />}><App /></ErrorBoundary>

// 2. Per route — a broken page keeps the header and navigation,
//    so the user can go somewhere else
{ path: 'problems', element: <ProblemList />, errorElement: <RouteError /> }

// 3. Around independent widgets — a failing recommendations panel
//    must not take down the article the user came to read
<ErrorBoundary fallback={<WidgetUnavailable />}>
  <Recommendations />
</ErrorBoundary>

// The fallback should offer a way forward: retry, go back, or
// reload — not just an apology.

// Reset on navigation, or the user is stuck on the error screen
// after moving to a different route.

// Test it: throw deliberately in development and confirm the
// fallback appears where you expect, not two levels up.

Key Points to Remember

  • 1An uncaught render error unmounts the entire React tree, leaving a blank page
  • 2Boundaries are still class components — getDerivedStateFromError renders the fallback, componentDidCatch logs
  • 3They do not catch event handlers, async code, timeouts or SSR errors, which need their own handling
  • 4Layer them: one around the app, one per route, and one around each independent widget
  • 5The fallback must offer a way forward, and boundaries should reset on navigation

Interview Questions

Sign in to ask Aria
1

What happens if a React component throws during render and there is no error boundary?

Medium
2

Why do error boundaries not catch errors in event handlers?

Hard
3

Where would you place error boundaries in a typical application?

Medium

Ask Aria about Error Boundaries

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…