Chapter 1 of 4

Why Boundaries Exist

What happens without one, and the two lifecycle methods that make one.

Since React 16, an error thrown during rendering unmounts the entire component tree. The reasoning is that a half-rendered interface is worse than none: a banking app showing the wrong balance is more dangerous than one showing an error.

An error boundary catches errors below it and renders a fallback instead, so the failure is contained to one part of the screen.

Boundaries must be class components

There is still no hook equivalent. A boundary implements one or both of two lifecycle methods.

class ErrorBoundary extends React.Component {
  state = { error: null };

  // Render phase: return the new state, must be pure
  static getDerivedStateFromError(error) {
    return { error };
  }

  // Commit phase: side effects such as logging belong here
  componentDidCatch(error, errorInfo) {
    logToService(error, errorInfo.componentStack);
  }

  render() {
    if (this.state.error) {
      return this.props.fallback ?? <p>Something went wrong.</p>;
    }
    return this.props.children;
  }
}

The division of labour

  • getDerivedStateFromError runs during rendering, so it must be pure - just return the state that shows the fallback.
  • componentDidCatch runs after the commit, so logging, analytics and anything with a side effect goes there. It also receives the component stack.
import { ErrorBoundary } from "react-error-boundary";

<ErrorBoundary
  FallbackComponent={ErrorFallback}
  onReset={() => refetch()}
>
  <Dashboard />
</ErrorBoundary>