DEV Community

Neha Sharma
Neha Sharma

Posted on • Edited on

Mastering Error Boundaries in React: How to Catch & Handle Errors Effectively

Image description

React JS provides errorBoundary to handle the unexpected errors by providing fallback UI.

Think of Error boundary is catch as we have in JavaScript.

You can watch my video on error boundary at my YouTube channel - Here

Problem Statement

In our ReactJS application, when we have components which are 3rd party dependent or have API calls or DB data. There could be unexpected situation where because of the some issue from 3rd party or things beyond our control, our application might face error. As a result, our application will crash and users will be left with a white screen.

Image description

We should be avoiding white screen. We should provide the user with an error message or next steps.

Error boundary helps us in replacing white screen with meaning full , and custom error messages.

Image description

Solution

In ReactJS native, there is ErrorBoundary. This is class based component and has limited flexibility.

One can use 'react-error-boundary' package. The reasons to go with react-error-boundary are:

  1. Can work with functional component

  2. Provide more flexibility

  3. Can log the errors and integrate with 3rd party eg: sentry

  4. Can provide reset app functionality.

code time

Please check the code here.

Install npm i react-error-boundary

1 . *Inline Fallback *

// App.jsx
import React, { useState, useEffect } from "react";
import { ErrorBoundary } from "react-error-boundary";
import CardList from "./CardList";

function App() {
  const [characters, setCharacters] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("https://rickandmortyapi.com/api/character")
      .then((response) => response.json())
      .then((data) => {
        setCharacters(data.results);
        setLoading(false);
      })
      .catch((error) => console.error("Error fetching data:", error));
  }, []);

  return (
    <div className="bg-gray-100 min-h-screen p-8">
      <h1 className="text-4xl font-bold text-center mb-8 text-gray-800">
        Rick and Morty Characters: Error Boundary Example
      </h1>
      {loading ? (
        <p className="text-center text-gray-500">Loading...</p>
      ) : (
        <ErrorBoundary fallback={<h1>Error</h1>}>
          <CardList characters={characters} />
        </ErrorBoundary>
      )}
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

2 . Fallback component

Create a component with your desire message, and UI.

// App.jsx
import React, { useState, useEffect } from "react";
import { ErrorBoundary } from "react-error-boundary";
import CardList from "./CardList";
import ErrorFallback from "./ErrorFallback";

function App() {
  const [characters, setCharacters] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("https://rickandmortyapi.com/api/character")
      .then((response) => response.json())
      .then((data) => {
        setCharacters(data.results);
        setLoading(false);
      })
      .catch((error) => console.error("Error fetching data:", error));
  }, []);

  return (
    <div className="bg-gray-100 min-h-screen p-8">
      <h1 className="text-4xl font-bold text-center mb-8 text-gray-800">Rick and Morty Characters: Error Boundary Example</h1>
      {loading ? (
        <p className="text-center text-gray-500">Loading...</p>
      ) : (
        <ErrorBoundary
          FallbackComponent={ErrorFallback}
        >
          <CardList characters={characters} />
        </ErrorBoundary>
      )}
    </div>
  );
}
export default App;
Enter fullscreen mode Exit fullscreen mode
// ErrorFallBack.jsx

function ErrorFallback({ error, resetErrorBoundary }) {
    return (
      <div className="text-center p-8">
        <h1 className="text-3xl font-bold text-red-600">Something went wrong!</h1>

      </div>
    );
  }

  export default ErrorFallback;
Enter fullscreen mode Exit fullscreen mode

3 . Logging

You can log the error and error information to your console, backend, file, or integration with any 3rd party API or application such as sentry.

//App.jsx
  function logErrorToService(error, errorInfo) {
    // Here you can send the error to an external logging service
    console.error("Logging to error service: ", { error, errorInfo });
  }
Enter fullscreen mode Exit fullscreen mode

Image description

4 . Reset

In this, we can let user to re-try by clicking on button. In our example, we are doing an API request. So, we can re- request the API request on click of Button.

// App.jsx

import React, { useState, useEffect } from "react";
import { ErrorBoundary } from "react-error-boundary";
import CardList from "./CardList";
import ErrorFallback from "./ErrorFallback";

function App() {
  const [characters, setCharacters] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("https://rickandmortyapi.com/api/character")
      .then((response) => response.json())
      .then((data) => {
        setCharacters(data.results);
        setLoading(false);
      })
      .catch((error) => console.error("Error fetching data:", error));
  }, []);

  function logErrorToService(error, errorInfo) {
    // Here you can send the error to an external logging service
    console.error("Logging to error service: ", { error, errorInfo });
  }


  return (
    <div className="bg-gray-100 min-h-screen p-8">
      <h1 className="text-4xl font-bold text-center mb-8 text-gray-800">Rick and Morty Characters: Error Boundary Example</h1>
      {loading ? (
        <p className="text-center text-gray-500">Loading...</p>
      ) : (
        <ErrorBoundary
          FallbackComponent={ErrorFallback} 
          onError={(error, errorInfo) => logErrorToService(error, errorInfo)}
          onReset={() => {
            setLoading(true);
            fetch("https://rickandmortyapi.com/api/character")
              .then((response) => response.json())
              .then((data) => {
                setCharacters(data.results);
                setLoading(false);
              });
          }}
        >
          <CardList characters={characters} />
        </ErrorBoundary>
      )}
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
// ErrorComponentFallback.jsx
function ErrorFallback({ error, resetErrorBoundary }) {
    return (
      <div className="text-center p-8">
        <h1 className="text-3xl font-bold text-red-600">Something went wrong!</h1>
        <p className="text-gray-500 mt-4">Error: {error.message}</p>
        <button
          onClick={resetErrorBoundary}
          className="mt-4 bg-blue-500 text-white p-2 rounded"
        >
          Try Again
        </button>
      </div>
    );
  }

  export default ErrorFallback;

Enter fullscreen mode Exit fullscreen mode

Where to use Error Boundary

You will be tempted to wrap every component with error boundary. However, there is little cost of the performance you have to bear. So, prefer to identify the components which could be "error sensitive". A few places where you should be using Error Boundary:

  1. Components with 3rd party api and data

  2. In most real-world applications, error boundaries are only placed around critical parts of the application (e.g., around major components or entire routes), so the performance impact is minimal.

  3. Prefer to wrap the parent component with Error boundary.

Where not to use error boundary

Error boundaries only catch rendering errors, lifecycle method errors, and errors in constructors, not in event handlers.

Do not use errorBoundary in event handlers. You can use try-catch.

A note on performance

Whenn an error is caught, React needs to re-render the components in the tree that are within the error boundary’s scope, which can introduce a slight delay as it tries to render the fallback UI.

If you use too many error boundaries throughout the application (at a very granular level), this can lead to unnecessary re-renders and increased component complexity, slightly degrading performance.

But it is always about trade-off, stability of app vs performance. If the performance cost is not much always go for the stability of the app or try to balance both.

Like it? Spread the knowledge and do say hi to me at twitter

Happy Learning

Top comments (0)