Type to generate custom UI components with AI

Type to generate UI components from text

OR

Browse thousands of MUI, Tailwind, React components that are fully customizable and responsive.

Explore Components

Better Error Handling and App Stability with React ErrorBoundary

In developing an application, many React developers do not inherently prioritize robustness. This oversight leads to JavaScript errors that can corrupt a React component’s internal state and break an application. Unhandled exceptions display cryptic errors that users can’t understand or a blank screen. Initially, React applications couldn’t handle these errors gracefully or recover from them. However, the advent of React 16 introduced the concept of errorboundary for handling React errors.

Error boundaries act as a safety net to catch errors anywhere in the child component tree, log these errors, and display a fallback user interface (UI) instead. This feature allows developers to improve the user experience by displaying a clear and informative error the user can relate to without the application crashing.

This article will discuss the concept of errorboundary, the different ways of managing it, the design patterns to consider, and its merits and limitations in React applications.

Understanding the Concept of Error Boundary in React: Overview

Error boundaries are React components built with a powerful mechanism for handling errors in a React application. They may encapsulate the top-level route components to handle application-wide errors or individual widgets to prevent errors from propagating to the rest of the application. An error boundary functions similarly to JavaScript `catch {}` block. It provides two lifecycle methods that are exclusive to class components for managing errors in React:

  • The `componentDidCatch` method to catch and log error information.

  • The `getDerivedStateFromError` method renders a fallback UI after an error occurs.

React error boundaries catch errors and display a fallback UI, making it an effective tool for improving a React application’s stability. Nevertheless, the subsequent section discusses the working principle of error boundaries to aid in efficiently managing React errors.

But before that, visit Purecode AI and explore our AI-powered code automation platform built to help developers improve their workflow. It houses a library of UI customizable components tailored to your React project needs.

A view of Purecode AI's landing page

Principle Behind ErrorBoundary

Error boundaries in React act as safety shields to catch JavaScript errors anywhere in the component tree, preventing them from crashing the entire application. When an error occurs during rendering, lifecycle methods, etc, React tries to identify the nearest errorboundary and triggers `componentDidCatch` or `getDerivedStateFromError` lifecycle methods if defined. Invoking the `getDerivedStateFromError` method updates the state in response to an error and displays an error message to the user whereas `componentDidCatch` logs the error.

An illustration of error boundaries error handling mechanism

Reasons for Using ErrorBoundary

Previously, we established that in the absence of an errorboundary to catch JavaScript errors, React unmounts the whole component tree, resulting in a blank screen. This default behavior may be a buzz kill for users of your application. Therefore, it’s essential to understand the significance of error boundaries in building robust and resilient applications. Let’s explore the reasons for adding error boundaries to your React application:

  • Preventing application crashes: When an error occurs, error boundaries handle it gracefully, preventing it from propagating to the rest of the component and crashing the application.

  • Improving user experience: React identifies and uses error boundaries to catch and handle errors rather than displaying a blank screen. By utilizing error boundaries, developers can ensure they display a clear error message to the end user, improving user experience.

  • Isolating errors: By encapsulating specific widgets of a React application with error boundaries, we can isolate errors without affecting the functionality of the rest of the application.

  • Streamlines debugging: Error boundaries ensure consistent error handling in React. They facilitate debugging errors by capturing and logging error details, improving productivity and efficiency.

Differences Between Try/catch and ErrorBoundary in React

The `try/catch` statement handles errors that may stop JavaScript code execution. It uses the `catch` statement to specify the next course of action after an error occurs in the `try` block. Consider the following syntax:

try {
  //tryStatements
} catch (exceptionVar) {
  //catchStatements
}

Catching and managing errors with error boundaries and the `try/catch` statement guarantees a robust mechanism for dealing with exceptions. However, they differ in how they operate. The table below illustrates these differences:

Error boundaryTry/catch
Error boundary works well with the declarative nature of React.Try/catch works well with imperative code.
Error boundaries possess limited error-catching capabilities outside of React rendering and lifecycle methods.Try/catch can handle synchronous errors within the block of code it was implemented.
Error boundaries are aware of the component hierarchy.Try/catch do not know the component tree.

Implementing An Error Boundary

In the ever-evolving landscape of React error handling, two distinct methods exist to address errors within React applications. These approaches include

  • Implementing custom error boundaries

  • Using react-error-boundary

Utilizing Custom ErrorBoundary

In React, we can create an error boundary using a class component. React provides essential lifecycle methods, such as `getDerivedStateFromError` and `componentDidCatch` to manage errors Nevertheless, for a class component to become an error boundary, it must define either (or both) of the lifecycle methods `static getDerivedStateFromError` or `componentDidCatch`. The example below shows how to create a custom error boundary in React:

//ErrorBoundary.jsx

import React from "react";

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

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

  componentDidCatch(error, errorInfo) {
    console.log(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

export default ErrorBoundary
import ErrorBoundary from "./ErrorBoundary"

// Usage in a component
function App () {
    return (
      <ErrorBoundary>
        <div>
	  <p>Check for errors</p>
	</div>
      </ErrorBoundary>
    );
}

export default App

Using react-error-boundary

The react-error-boundary library streamlines the integration of error boundaries and error handling in React. It also provides an effective solution to the limitations of the error boundary created with a class component. The react-error-boundary provides a few Application Programming Interface (API) to incorporate an error boundary successfully, including:

  • `ErrorBoundary` component

  • `useErrorBoundary` hook

  • `withErrorBoundary` HOC

Due to the recent shift from class to function components, the React team recommends using the react-error-boundary package as the best practice for handling errors in React. To get started, ensure to install the package. Consider the following code example:

npm install react-error-boundary
//ExampleUI.jsx

function ExampleUI() {
  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <p>Example UI</p>
    </div>
  );
}

export default ExampleUI
import React, { useEffect } from "react";
import { ErrorBoundary } from "react-error-boundary";
import ExampleUI from "./ExampleUI"

function fallbackRender({ error, resetErrorBoundary }) {

  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre style={{ color: "red" }}>{error.message}</pre>
    </div>
  );
}

function App() {
  useEffect(() => {
    let timer;

    timer = setTimeout(() => {
      throw new Error("This timer is faulty");
    }, 2000);

    return () => {
      clearTimeout(timer);
    };
  }, []);

  return (
    <ErrorBoundary
      fallbackRender={fallbackRender}
      onReset={(details) => {
        // Reset the state of your app so the error doesn't happen again
      }}
    >
      <ExampleUI />
    </ErrorBoundary>
  );
}

export default App;

In the example above, we used the `ErrorBoundary` component from react-error-boundary to catch the error that was thrown after two seconds, as illustrated below:

Moving forward, we’ll delve deeper into optimizing error handling with the error boundary package. To gain further insight into this library, we recommend watching the tutorial video provided below:

Managing React Error Boundary

The react-error-boundary provides a modern and convenient way to handle errors detected during render or in component lifecycle methods. By utilizing the APIs it supports, developers can achieve the following:

  • Catch all errors

  • Reset an error boundary or recover from errors

Catching all Errors

When developing an errorboundary, detecting and handling all errors in React is essential to the user experience. Although this is wishful thinking, traditional error boundaries cannot catch exceptions outside React rendering and lifecycle methods. According to the React docs:

Error boundaries do not catch errors for:

Nevertheless, the react-error-boundary exposes the `useErrorBoundary` hook to cover these edge cases. Consider the following example:

// AddTodo.jsx

import React, { useState } from "react";
import { useErrorBoundary } from "react-error-boundary";

function AddTodo() {
  const [todo, setTodo] = useState("");
  const { showBoundary } = useErrorBoundary();

  const submitHandler = async (e) => {
    e.preventDefault();
    try {
      const res = await fetch("https://jsonplaceholder.typicode.com/todos", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: { title: todo, completed: "false" },
      });

      const data = await res.json();
      if (!data.id) {
        throw new Error("Unable to add todo");
      }

      setTodo("");
    } catch (error) {
      const message =
        error instanceof Error ? error.message : "Something went wrong";
      console.log(message);
      showBoundary(error);
    }
  };
  return (
    <div
      style={{
        padding: "1rem",
        maxWidth: "400px",
        width: "100%",
        margin: "0 auto",
      }}
    >
      <form onSubmit={submitHandler}>
        <div>
          <input
            style={{ display: "block", width: "100%", padding: "0.5rem" }}
            type="text"
            value={todo}
            onChange={(e) => {
              setTodo(e.target.value);
            }}
            name=""
            id=""
          />
        </div>
        <button
          style={{
            marginTop: "1rem",
            display: "block",
            width: "100%",
            padding: "0.5rem",
            backgroundColor: "#333333",
            color: "white",
          }}
          type="submit"
        >
          Add
        </button>
      </form>
    </div>
  );
}

export default AddTodo
import React from "react";
import { ErrorBoundary } from "react-error-boundary";
import AddTodo from "./AddTodo"

function fallbackRender({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre style={{ color: "red" }}>{error.message}</pre>
    </div>
  );
}

function App() {
  return (
    <ErrorBoundary fallbackRender={fallbackRender}>{<AddTodo />}</ErrorBoundary>
  );
}

export default App;

In the example above, we used the function exposed by the `useErrorBoundary` hook to show errors caught inside the form submit event handler, as illustrated below:

Resetting an ErrorBoundary

Often, users get stuck with an error page, making them trigger a reload. The react-error-boundary mitigates this issue by supporting a built-in error recovery and retry feature. This feature empowers applications with the tools to recover from errors and allows users to retry the failed operation. Key components of this feature include the following:

  • The `onReset` prop

  • The `resetErrorBoundary` method

Consider the following example when resetting an error:

// ExampleUI.jsx

function ExampleUI({ value }) {
  if (value === "Hi") {
    throw new Error("Oh no why");
  }

  return (
    <div
      style={{
        height: "100vh",
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
      }}
    >
      <p>{value}</p>
    </div>
  );
}

export default ExampleUI
// Fallback.jsx

function Fallback({ error, resetErrorBoundary }) {
  useEffect(() => {
    let timer;

    timer = setTimeout(() => {
      resetErrorBoundary();
    }, 5000);

    return () => {
      clearTimeout(timer);
    };
  }, []);

  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre style={{ color: "red" }}>{error.message}</pre>
    </div>
  );
}

export default Fallback
import React, { useEffect, useState } from "react";
import { ErrorBoundary } from "react-error-boundary";
import ExampleUI from "./ExampleUI";
import Fallback from "./Fallback";

function App() {
  const [input, setInput] = useState("");
  return (
    <>
      <div>
        <input
          type="text"
          value={input}
          placeholder="Type something but not Hi"
          onChange={(e) => {
            setInput(e.target.value);
          }}
        />
      </div>
      <ErrorBoundary
        FallbackComponent={Fallback}
        onReset={(details) => {
          setInput("");
        }}
        resetKeys={[input]}
      >
        <ExampleUI value={input} />
      </ErrorBoundary>
    </>
  );
}

export default App;

In the example, typing “Hi” caused an error. The error fallback was unmounted after 5 seconds using the reset feature, allowing the user to type something else. Below is an illustration of this:

Advantages of React error boundary

The react-error-boundary has become an invaluable library in the React ecosystem. It offers several merits that make it ideal for handling errors in React. Incorporating this library allows developers to achieve the following:

  • Enhanced user experience

  • Simplified error management

  • Streamlined integration

  • Customizable fallback UI

  • Improves debugging

  • Community support and maintenance

Enhanced User Experience

The React community introduced error boundaries to improve the user experience by handling errors detected in the component tree. Nevertheless, react-error-boundary refines this process, allowing developers to customize the error UI to display user-friendly information, leading to a smoother and more reliable user experience.

Simplified Error Management

The react-error-boundary provides simple and intuitive APIs that abstract away the complexities of error management code. These APIs help developers conveniently handle errors in functional components, asynchronous operations, and event handlers.

Streamline Integration

The react-error-boundary library streamlines the integration of error boundaries into modern React apps by leveraging hooks, aligning with current React development practices. It also simplifies its incorporation into existing applications with legacy React codes.

Customizable Fallback UI

The react-error-boundary allows developers to customize their fallback component after detecting an error. It provides flexibility in designing user-friendly error messages tailored to the project’s needs.

Improves Debugging

The `onError` prop, supported by the `ErrorBoundary` component logs, reports caught exceptions to the error reporting service. This feature provides valuable information for debugging and resolving issues.

Community Support and Maintenance

The react-error-boundary is a popular and well-maintained library in the React community. It benefits from regular updates, contributions, and ongoing maintenance, ensuring compatibility with the latest React trends.

Design Patterns for Error Boundary in React

There are a variety of design patterns to use while implementing error boundaries in a React application. These patterns depend on the error-handling requirements and the project’s architecture. However, the two most popular error-handling design patterns include the following:

  • Component-level error boundary

  • Layout-level error boundary

Component-level Error Boundary

Component-level error boundary involves wrapping error boundaries around individual components or groups of related widgets. This method provides fine-grained error handling and isolation, ensuring errors affect other components. This is shown in the code example below:

<>
  <ErrorBoundary>
    <Sidebar />
  </ErrorBoundary>
  <Header />
  <MainContent />
  <Footer />
</>

The ability of error boundaries to catch errors ensures the error does not propagate upward in the component tree. This limits their impact to only the buggy component.

Layout-level Error Boundary

Layout-level error boundaries are top-level error boundaries that encapsulate the entire application layout or specific sections of the UI. When an error occurs, the layout-level error barrier intercepts it and displays a fallback UI for that section or group of UI components.

However, layout-level error boundaries offer a broader scope compared to their component-level counterparts, which are more granular. Errors caught in one component are critical to the other components in the layout. Consider the following code example:

<>
  <Sidebar />
  <ErrorBoundary>
    <Hero />
    <MainContent />
    <Footer />
  </ErrorBoundary>
</>

Testing an Error Boundary Component

In a previous section, we established that error boundaries cannot catch errors that occur within themselves. Therefore, proper testing is essential to ensure robust error boundaries in React.

Testing error boundaries involves verifying its behavior under different error scenarios. By employing testing frameworks like Jest along with a utility like React testing library or Cypress, developers can effectively simulate these error scenarios. Below is an example to illustrate this functionality with Jest and Cypress:

import { render } from "@testing-library/react";
import ErrorBoundary from "../ErrorBoundary";
import BuggyComponent from "../BuggyComponent";

it("catches error and renders message", () => {
  console.error = jest.fn();

  render(
    <ErrorBoundary>
      <BuggyComponent />
    </ErrorBoundary>
  );

  expect(screen.getByText("Something went wrong.")).toBeInTheDocument();
});
describe('ErrorBoundary Component', () => {
  it('Displays fallback UI when an error occurs', () => {
    cy.visit('/your-buggy-page'); 

    // Simulate an error in the component
    cy.get('[data-testid="error-trigger"]').click();

    // Assert that the fallback UI is displayed
    cy.get('[data-testid="fallback-ui"]').should('be.visible');
  });

  it('Recovers from errors and displays the correct content', () => {
    cy.visit('/your-buggy-page');

    // Simulate an error in the component
    cy.get('[data-testid="error-trigger"]').click(); 

    // Trigger the recovery mechanism (if any)
    cy.get('[data-testid="retry-button"]').click();

    // Assert that the correct content is displayed after recovery
    cy.get('[data-testid="recovered-content"]').should('be.visible'); 
  });
});

Final Notes

Error boundaries play a crucial role in proper error handling and stability of an application. They possess a powerful mechanism for detecting errors and instantly dealing with them before propagating to the rest of the application. Furthermore, considering the constrained scope of bug detection inherent in traditional error boundaries and in alignment with recent trends in React development, the React team advocates utilizing the react-error-boundary as the preferred approach.

This library provides a modern and convenient way to handle errors. By utilizing the customization options offered by react-error-boundary, developers can display user-friendly information, leading to a smoother and more reliable user experience.

In addition, visit Purecode AI to explore our state-of-the-art code automation platform designed to enhance developer productivity. Discover how Purecode AI can streamline your development process and elevate your project to new heights.

Additional Readings

If this article made an impact, consider reading other great articles from our blog to elevate your React knowledge:

Ofili Chukwuemeka Timothy

Ofili Chukwuemeka Timothy