LearnContact
Lesson 4422 min read

Error Boundaries

Learn how error boundaries catch rendering errors in their child component tree and display a fallback UI instead of letting the whole app crash to a blank screen.

Introduction

JavaScript errors happen — a field is unexpectedly undefined, an API returns a different shape than expected, a third-party library throws. In a well-behaved application, one broken widget should not take down the entire page. But by default, that is exactly what React does.

This lesson introduces error boundaries: special components that catch JavaScript errors thrown anywhere in their child component tree during rendering, log them, and display a fallback UI instead of leaving users staring at a blank screen.

What You Will Learn
  • Why an uncaught rendering error crashes the entire React app by default.
  • How to build a class-based error boundary with getDerivedStateFromError().
  • How wrapping specific sections in an error boundary isolates failures.
  • What error boundaries cannot catch, like event handler and async errors.
  • How the react-error-boundary library simplifies this pattern.

The Problem: One Error Crashes Everything

React renders your component tree top to bottom. If any component throws an error while rendering — say, calling .toUpperCase() on a value that turned out to be undefined — React does not know how to safely continue, so by default it unmounts the entire component tree, leaving a blank white screen with no explanation for the user.

A Component That Crashes
function UserBadge({ user }) {
  // If "user" is ever undefined, this line throws
  return <span>{user.name.toUpperCase()}</span>;
}
Without an Error Boundary

One bad UserBadge render can blank out an entire dashboard, including totally unrelated sections like the sidebar and header, even though only one small widget actually failed.

Building an Error Boundary

An error boundary must currently be a class component, because it relies on two lifecycle methods that do not have Hook equivalents: static getDerivedStateFromError(), which updates state so the next render shows a fallback UI, and componentDidCatch(), which is typically used for logging.

ErrorBoundary.jsx
import { Component } from 'react';

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render shows the fallback UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // Log the error to a reporting service
    console.error('ErrorBoundary caught:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h2>Something went wrong. Please try again later.</h2>;
    }

    return this.props.children;
  }
}

export default ErrorBoundary;
When a Child Throws
Before error: renders this.props.children normally
After error: getDerivedStateFromError sets hasError = true
Next render: shows "Something went wrong. Please try again later."

Wrapping Parts of the App

Rather than wrapping your entire app in a single error boundary, it is often better to wrap individual risky or independent sections separately. That way, if the "Comments" widget crashes, the rest of the page — the article, the sidebar, the header — keeps working normally.

Dashboard.jsx
import ErrorBoundary from './ErrorBoundary';
import Sidebar from './Sidebar';
import ActivityFeed from './ActivityFeed';
import Comments from './Comments';

function Dashboard() {
  return (
    <div className="dashboard">
      <Sidebar />

      <ErrorBoundary>
        <ActivityFeed />
      </ErrorBoundary>

      <ErrorBoundary>
        <Comments />
      </ErrorBoundary>
    </div>
  );
}

export default Dashboard;
Isolate, Do Not Blanket

Wrapping each independent section in its own ErrorBoundary means a crash in Comments only replaces the Comments section with a fallback, while Sidebar and ActivityFeed keep working.

What Error Boundaries Do Not Catch

Error boundaries only catch errors thrown during rendering, in lifecycle methods, and in constructors of the components below them. They intentionally do not catch every kind of error in your app.

Event Handlers

An error thrown inside an onClick handler does not trigger the boundary — use a regular try/catch there instead.

Asynchronous Code

Errors inside setTimeout callbacks, promises, or async functions are not caught by error boundaries.

Server-Side Rendering

Error boundaries do not catch errors that occur during server-side rendering.

The Boundary Itself

An error thrown inside the error boundary's own render method is not caught by that same boundary.

The react-error-boundary Library

Writing the same class boundary by hand in every project gets repetitive. The popular react-error-boundary package provides a ready-made <ErrorBoundary> component with a friendlier API, including a "resetKeys" option to automatically recover once the underlying data changes.

Using react-error-boundary
import { ErrorBoundary } from 'react-error-boundary';

function Fallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong: {error.message}</p>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={Fallback}>
      <Comments />
    </ErrorBoundary>
  );
}

Why Use the Library

  • No need to hand-write a class component with getDerivedStateFromError yourself.
  • The FallbackComponent receives the actual error and a resetErrorBoundary function for a "Try again" button.
  • It still follows the exact same underlying rules and limitations as a hand-built error boundary.

Common Mistakes

Avoid These Mistakes
  • Expecting an error boundary to catch errors thrown inside onClick or other event handlers — it will not.
  • Expecting an error boundary to catch errors inside async code like fetch() callbacks or setTimeout.
  • Wrapping the entire app in exactly one error boundary, so any failure blanks out everything at once.
  • Forgetting componentDidCatch entirely, missing the chance to log errors to a monitoring service.

Best Practices

  • Wrap independent sections of the UI in their own error boundaries instead of one giant boundary.
  • Always log caught errors in componentDidCatch (or the library's equivalent) so failures are visible to your team.
  • Handle errors from event handlers and async code with plain try/catch, since boundaries will not catch those.
  • Reach for the react-error-boundary library in real projects to avoid rewriting the same class boilerplate.
  • Give fallback UIs a way to recover, like a "Try again" button, rather than a permanent dead end.

Frequently Asked Questions

Can I write an error boundary as a function component?

Not directly with plain React — getDerivedStateFromError and componentDidCatch only exist on class components. Libraries like react-error-boundary give you a function-component-friendly API around the same underlying class.

Why does my error boundary not catch a click handler error?

Error boundaries only catch errors thrown during rendering, not inside event handlers, which run outside of React's render cycle. Use a try/catch inside the handler instead.

Should I use one error boundary for my whole app?

You typically want at least one top-level boundary as a safety net, plus smaller boundaries around independent sections so a single failure does not take down everything.

What does getDerivedStateFromError actually do?

It is a static lifecycle method React calls after a descendant throws during render; it returns a state update that the boundary uses to render its fallback UI on the next render.

Is react-error-boundary required, or can I write my own?

It is optional. You can always write your own class-based boundary, but the library saves boilerplate and adds conveniences like resetKeys and a ready-made fallback API.

Key Takeaways

  • By default, an uncaught rendering error crashes the entire React app to a blank screen.
  • An error boundary is a class component using getDerivedStateFromError() to render a fallback UI after a child throws.
  • Wrapping independent sections in separate boundaries isolates failures instead of crashing the whole page.
  • Error boundaries do not catch errors in event handlers, async code, or server-side rendering.
  • The react-error-boundary library provides a convenient, modern alternative to hand-writing the class boilerplate.

Summary

Error boundaries are React's built-in safety net for rendering errors, letting a single broken section fail gracefully instead of crashing the whole application. Understanding their limitations — no event handlers, no async code — is just as important as knowing how to build one.

In this lesson, you learned why uncaught render errors crash React apps, how to build a class-based error boundary, how to isolate failures by wrapping specific sections, and what error boundaries cannot catch. Next, you will learn how portals let you render content outside your component's normal DOM position.

Lesson 44 Completed
  • You understand why an uncaught render error crashes the whole app.
  • You can build a class-based error boundary using getDerivedStateFromError.
  • You can wrap independent sections in their own error boundaries.
  • You know what error boundaries do not catch, and about the react-error-boundary library.
Next Lesson →

Portals