DEV Community

Cover image for React Error Boundaries: Improving App Stability with Route-Level Error Management
Francisco Mendes
Francisco Mendes

Posted on • Updated on

React Error Boundaries: Improving App Stability with Route-Level Error Management

Introduction

One of the most important things for the user experience is the creation of mechanisms that allow the user to use the application with complete peace of mind.

We usually deal with errors in our application with Try-Catch, but this only applies to logic and data flow, the same does not apply to components. However, React components can throw a lot of things, be it JavaScript errors or loading state.

Sometimes when an error occurs in the app, be it rendering or not, and no matter how deep in the components tree it occurs, if it is not handled well, it can buble up and end up breaking the application.

And one of the things that must always be guaranteed is navigation in the application, even if a route is broken and that's why I decided to write today's article.

Assumed knowledge

The following would be helpful to have:

  • Basic knowledge of React
  • Basic knowledge of React Router

Getting Started

Project Setup

Run the following command in a terminal:

yarn create vite react-error-boundary --template react
cd react-error-boundary
Enter fullscreen mode Exit fullscreen mode

Now we can install the necessary dependencies:

yarn add react-router-dom
Enter fullscreen mode Exit fullscreen mode

Build the Pages

Now we have everything we need. The next step is to create the page components, starting with the Home.jsx:

// @src/pages/Home.jsx
const Home = () => {
  return (
    <div>
      <h1>Home page</h1>
    </div>
  );
};

export default Home;
Enter fullscreen mode Exit fullscreen mode

The last page we are going to create is Other.jsx, on this page we are going to have a function responsible for generating a number taking into account a certain range. And if the result is greater than 4, we will throw an error. It will only run when the component is mounted.

// @src/pages/Other.jsx
import { useCallback, useEffect } from "react";

const Other = () => {
  const randomIntFromInterval = useCallback((min, max) => {
    return Math.floor(Math.random() * (max - min + 1) + min);
  }, []);

  useEffect(() => {
    const num = randomIntFromInterval(1, 5);
    if (num > 4) {
      throw new Error("SOME RANDOM ERROR");
    }
  }, []);

  return (
    <div>
      <h1>Other page</h1>
    </div>
  );
};

export default Other;
Enter fullscreen mode Exit fullscreen mode

This way, there will always be a chance as soon as the route is visited to throw the error, which consequently breaks the page.

Build the Layout

We have the pages created, now we need to build the app's layout, which will hold elements that will be common between the various pages.

// @src/components/Layout.jsx
import { Link, Outlet } from "react-router-dom";

const Layout = () => (
  <div>
    <ul>
      <li>
        <Link to="/">Home</Link>
      </li>
      <li>
        <Link to="/other">Other</Link>
      </li>
    </ul>
    <Outlet />
  </div>
);

export default Layout;
Enter fullscreen mode Exit fullscreen mode

Next, we need to create another component, which will be responsible for rendering the error message whenever an error is caught by the Error Boundary.

// @src/components/ErrorBoundary.jsx
import { useRouteError } from "react-router-dom";

const ErrorBoundary = () => {
  const error = useRouteError();

  return (
    <section>
      <h1>Error Boundary</h1>
      <small>{error?.message}</small>
    </section>
  );
};

export default ErrorBoundary;
Enter fullscreen mode Exit fullscreen mode

In the above example we use the useRouteError() hook to get the error object in order to show a message to the user.

Router Setup

We already have the pages and components we need for today's example, the only thing left is to register the routes in the router. And assign the Error boundary to the route that throws the error (the ideal would be to add it to all routes).

// @src/App.jsx
import {
  Route,
  createBrowserRouter,
  createRoutesFromElements,
  RouterProvider,
} from "react-router-dom";

import HomePage from "./pages/Home";
import OtherPage from "./pages/Other";

import Layout from "./components/Layout";
import ErrorBoundary from "./components/ErrorBoundary";

const routesFromElements = createRoutesFromElements(
  <Route element={<Layout />}>
    <Route index element={<HomePage />} />
    <Route
      path="/other"
      element={<OtherPage />}
      errorElement={<ErrorBoundary />}
    />
  </Route>
);

const router = createBrowserRouter(routesFromElements);

export const App = () => {
  return <RouterProvider router={router} />;
};
Enter fullscreen mode Exit fullscreen mode

If you followed the article step by step, you should have an end result similar to the following:

image

Conclusion

As usual, I hope you enjoyed the article and that it helped you with an existing project or simply wanted to try it out.

If you found a mistake in the article, please let me know in the comments so I can correct it. Before finishing, if you want to access the source code of this article, I leave here the link to the github repository.

Top comments (0)