DEV Community

Linas Spukas
Linas Spukas

Posted on

Error Boundaries With React

Since React v16 a new concept of an error boundary was introduced to help to deal with errors in React. As you know, when an error is thrown within any component in the React tree, the whole app crashes, resulting in poor user experience and frustration. Now using special error boundary component, you can prevent crashing the app, render a fallback UI and save the day.

Syntax

Error boundaries catch errors during rendering, in lifecycle methods and constructors. To make a component an error boundary, it must be a class component and at least one of the two lifecycle methods declared: static getDerivedStateFromError() or componentDidCatch(). Each error handling lifecycle method runs on different phases and has its own purpose.

static getDerivedStateFromError(error) is called during the render phase and should do only one job - update the state if it's invoked. It receives one parameter, a thrown error.

componentDidCatch(error, errorInfo) is called during the commit phase. That means side effects are allowed in this method, so it's a great place to call error logging services. It receives two parameters, a thrown error and an object with component call stack to help trace where exactly the error was thrown from.

All combined we get the following error boundary component:

class ErrorBoundary extends React.Component {
  state = {
    hasError: false
  };

  static getDerivedStateFromError(error) {
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // call error logging or reporting service 
  }

  render() {
    if (this.state.hasError) {
      return <div>Fallback UI</div>;
    }

    return this.props.children;
  }
}

In case of an error, the component lifecycle will run in the following order:

  1. static getDerivedStateFromError() called and updates the state.
  2. render() method called to render the fallback UI.
  3. componentDidCatch() called with the same error, ready to handle side effects like logging or reporting.

Error boundaries also can be used in a server-side rendering, but only static getDerivedStateFromError() method is available, as server-side rendering does not have a commit phase.

Usage

Error boundary component will catch errors only from the child components and not within itself. The following example illustrates the wrong place to use boundary component:

function throwError() {
  throw new Error("some error");
}

function Footer() {
  return (
    // will not handle error
    <ErrorBoundary>
      <div>{throwError()}</div>
    </ErrorBoundary>
  );
}

A good place to use a boundary component to catch and handle an error:

function throwError() {
  throw new Error("some error");
}

function Footer() {
  return <div>{throwError()}</div>;
}

// Error will be handled
<ErrorBoundary>
  <Footer />
</ErrorBoundary>;

It is enough to declare the component once and reuse it all over the app.
Using it as the most top parent component is not the best idea, in the event of an error, the fallback UI will replace the whole app.
What usually desired is to render a fallback UI for the app part that is affected by the error, and render the rest of the app as expected. For example, if an error occurred in the footer of the webpage, there is no need to render fallback UI for the whole app, just the footer, and leave header, sidebar or whatever content needed to run further:

function throwError() {
  throw new Error("some error");
}

function Header() {
  return <div>Header content</div>;
}

function Footer() {
  return <div>{throwError()}</div>;
}

function App() {
  return (
    <>
      <ErrorBoundary>
        // still render after the Footer fails
        <Header />
      </ErrorBoundary>
      <ErrorBoundary>
        // render a fallback UI when Footer fails
        <Footer />
      </ErrorBoundary>
    </>
  );
}

Summing Up

Error boundary implementation improves user experience. When one part of the application throws an exception, it renders a fallback UI for it, does not crash the application, and keeps running not affected parts.
Only class components can be error boundaries at the moment, but hooks will be supported as well in the near future.

Top comments (0)