An Introduction to React Error Boundary

By Hemanta Sundaray on 2022-11-15

Consider the following app:

Counter App

We have two componets - <Counter1> & <Counter2>- rendered inside the <App /> component.

Counter1.js
import React, { useState } from "react";

const Counter1 = () => {
  const [count, setCount] = useState(0);

  return (
    <div className="w-80 mb-6 flex justify-between items-center border border-green-500 px-1 py-0.5">
      <button
        className=" bg-green-500 px-2 py-1"
        onClick={() => setCount((prevCount) => prevCount - 1)}
      >
        -
      </button>
      {count}
      <button
        className="bg-green-500 px-2 py-1"
        onClick={() => setCount((prevCount) => prevCount + 1)}
      >
        +
      </button>
    </div>
  );
};

export default Counter1;
Counter2.js
import React, { useState } from "react";

const Counter2 = () => {
  const [wrongCounter, setWrongCounter] = useState(false);

  if (wrongCounter === true) {
    throw new Error();
  }

  return (
    <div className="w-80 px-1 py-0.5 border border-red-500 flex justify-between items-center ">
      <button
        className="bg-red-500 px-2 py-1"
        onClick={() => setWrongCounter(true)}
      >
        -
      </button>
      0
      <button
        className="bg-red-500 px-2 py-1"
        onClick={() => setWrongCounter(true)}
      >
        +
      </button>
    </div>
  );
};

export default Counter2;
App.js
import React from "react";
import Counter1 from "./components/screens/Counter1";
import Counter2 from "./components/screens/Counter2";


const App = () => {
  return (
    <div className="w-1/2 m-auto mt-20 flex flex-col items-center">
      <Counter1 />
      <Counter2 />
    </div>
  );
};

export default App;

<Counter1> works perfectly fine. But <Counter2> is broken.

Counter 1

If we try to increase or decrease the counter in the <Counter2> component, the entire application crashes. We see a blank screen:

Blank Screen

That's because, clicking on the + or - button inside the <Counter2> component sets the value of the wrongCounter variable to true, which throws an error. The error then bubbles up to the parent <App> component and eventually the entire application crashes.

Wouldn't it be great if we can catch the error inside the <Counter2> component and show the error message in addition to the <Counter1> component (which is working perfectly fine)?

This is where React error boundary components come into the picture. They are React components that capture runtime errors in their child component tree. They isolate a component from the rest of the application and render a fallback UI when an error occurs.

Note: A runtime error is any error a program has while it is running.

Error boundaries are created as class components. However, in this post, we will use a package called react-error-boundary, which exports an ErrorBoundary component.

So, let's go ahead and install the package:

npm install react-error-boundary

Next, we import the ErrorBoundary component from react-error-boundary and render it with our component (<Counter2>) inside.

App.js
import React from "react";
import Counter1 from "./components/screens/Counter1";
import Counter2 from "./components/screens/Counter2";
import { ErrorBoundary } from "react-error-boundary";

const ErrorFallback = () => {
  return (
    <>
      <p>Whoops...an error occured!</p>
    </>
  );
};

const App = () => {
  return (
    <div className="w-1/2 m-auto mt-20 flex flex-col items-center">
      <Counter1 />
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <Counter2 />
      </ErrorBoundary>
    </div>
  );
};

export default App;

We pass a FallbackComponent prop to ErrorBoundary When ErrorBoundary catches an error, the ErrorFallback component renders instead.

Now, if we click on the + or - button in the <Counter2> component, our entire application does not crash. The ErrorBoundary component catches the error thrown inside the <Counter2> component and renders the text Whoops...an error occured!.

Error Message

When ErrorBoundary renders a fallback component, it will pass in two values as props: error & resetErrorBoundary.

  • error is the Error object itself which can be useful if we want to display the error message.
  • resetErrorBoundary is a callback function that will reset the internal error state of the ErrorBoundary itself.

Let's modify the ErrorFallback component. In the parameter list, we will destructure the resetErrorBoundary prop. Below the <p> element, we will add in a <button> element with the text Reset and pass the resetErrorBoundary function as the error handler.

App.js
// Rest of the code

const ErrorFallback = ({ resetErrorBoundary }) => {
  return (
    <>
      <p>Whoops...an error occured!</p>
      <button
        className="bg-gray-100 rounded px-1 py0.5"
        onClick={resetErrorBoundary}
      >
        Reset
      </button>
    </>
  )
}

// Rest of the code

Reset Button

With this code in place, when we click on the Reset button in our fallback UI, the ErrorBoundary will reset and attempt to rerender the broken child component.

Before we wrap up, let's discuss when and where to use error boundaries to maximize their potential impact and their limitations.

We should put error boundaries as close to the source of potential errors as possible. If our error boundary is too far from the source of the error, then other nearby components will be swallowed up by the error.

It is important that we remain aware of some of the key limitations of error boundaries. Note that Error boundaries don't catch errors for:

  • Event handlers
  • Asynchrnous code
  • Server side rendering
  • Errors thrown in the error boundary itself

Join the Newsletter