214

I can make a class an error boundary in React by implementing componentDidCatch.

Is there a clean approach to making a functional component into an error boundary without converting it into a class?

Or is this a code smell?

4
  • 4
    So, componentDidCatch is probably considered part of the React lifecycle hooks, which is not available in simple function components. The only way I imagine you could do something similar would be to wrap the entirety of your function body in a try...catch block... Commented Jan 28, 2018 at 4:36
  • 6
    A note about Alexander's comment: there is a difference between error boundaries and try/catch: when you write <Comp/> ... Comp is not rendered yet, the expression only constructs the JSX tree. Doing a try { <Comp /> } catch { ... } will not catch any Comp render error because there is no rendering yet Commented Mar 25, 2020 at 17:49
  • 5
    Now react version is 17.01 and still, they say Only class components can be error boundaries Commented Feb 4, 2021 at 5:19
  • 4
    React 18 has been released, and still they say the same :-( Commented Apr 22, 2022 at 6:32

8 Answers 8

121

As of v19.2.0, there's no way to turn a functional component into an error boundary.

The React docs are clear about that, although you're free to reuse them as many times as you wish:

To implement an Error Boundary component, you need to provide static getDerivedStateFromError which lets you update state in response to an error and display an error message to the user. You can also optionally implement componentDidCatch to add some extra logic, for example, to log the error to an analytics service.

There is currently no way to write an Error Boundary as a function component. However, you don’t have to write the Error Boundary class yourself. For example, you can use react-error-boundary instead.

Also bear in mind that try/catch blocks won't work on all cases.
If a component deep in the hierarchy tries to updates and fails, the try/catch block in one of the parents won't work -- because it isn't necessarily updating together with the child.

Sign up to request clarification or add additional context in comments.

1 Comment

so what is your recommended alternative?
30

As mentioned already, the React team has not yet implemented a hook equivalent, and there are no published timelines for a hook implementation.

A few third party packages on npm implement error boundary hooks. I published react-use-error-boundary, attempting to recreate an API similar to useErrorBoundary from Preact:

import { withErrorBoundary, useErrorBoundary } from "react-use-error-boundary";

const App = withErrorBoundary(({ children }) => {
  const [error, resetError] = useErrorBoundary(
    // You can optionally log the error to an error reporting service
    (error, errorInfo) => logErrorToMyService(error, errorInfo)
  );

  if (error) {
    return (
      <div>
        <p>{error.message}</p>
        <button onClick={resetError}>Try again</button>
      </div>
    );
  }

  return <div>{children}</div>;
});

Comments

26

There is an implementation that can handle with non-existent functionalities for a functional component such as componentDidCatch and deriveStateFromError.

According to the author, it is based on React.memo().

The proposed solution is greatly inspired by the new React.memo() API.

import Catch from "./functional-error-boundary"

type Props = {
  children: React.ReactNode
}

const MyErrorBoundary = Catch(function MyErrorBoundary(props: Props, error?: Error) {
  if (error) {
    return (
      <div className="error-screen">
        <h2>An error has occured</h2>
        <h4>{error.message}</h4>
      </div>
    )
  } else {
    return <React.Fragment>{props.children}</React.Fragment>
  }
})

reference and API here

Comments

18

What I have done is create custom class component and wrapped my functional/class component inside it where ever its required. This is how my custom class component looks like:

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

  componentDidCatch(error) {
    this.setState({error: `${error.name}: ${error.message}`});
  }

  render() {
    const {error} = this.state;
    if (error) {
      return (
        <div>{error}</div>
      );
    } else {
      return <>{this.props.children}</>;
    }
  }
}

And used it like this my functional/class components:

<ErrorBoundary key={uniqueKey}>
   <FuncationalOrChildComponent {...props} />
</ErrorBoundary>

PS: As usual the key property is very important as it will make sure to re-render the ErrorBoundary component if you have dynamic child elements.

1 Comment

i dont understand what you mentioned about key property is very important as it will make sure to re-render the ErrorBoundary component if you have dynamic child elements. Could you please explain a more on this statement. thanks
13

Official React team not provided Error boundary support for functional component. We can achieve error boundary for functional component using npm package. https://www.npmjs.com/package/react-error-boundary

Comments

3

There are some great third party libraries for this but if you are already using Sentry then you can use Sentry.ErrorBoundary

docs

import React from "react";
import * as Sentry from "@sentry/react";

<Sentry.ErrorBoundary fallback={<p>An error has occurred</p>}>
  <Example />
</Sentry.ErrorBoundary>;

or as a higher order component

import React from "react";
import * as Sentry from "@sentry/react";

Sentry.withErrorBoundary(Example, { fallback: <p>an error has occurred</p> });

Comments

3

If you are coming to this page in 2023, are using TypeScript and do not want to implement a dependency there is a pretty simple but effective example over on Netlify of how to implement a simple error boundary.

Sadly there is still no functional solution to the problem as per the react docs.

Netlify example: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/error_boundaries/

Comments

-1
import React, { useState, useEffect } from 'react';
import { Button } from 'antd'; 

const ErrorBoundary = ({ children }) => {
  const [hasError, setHasError] = useState(false);

  useEffect(() => {
    const errorHandler = (event) => {
      setHasError(true);
      console.error("ErrorBoundary caught an error", event.error);
    };

    window.addEventListener("error", errorHandler);
    window.addEventListener("unhandledrejection", errorHandler);

    return () => {
      window.removeEventListener("error", errorHandler);
      window.removeEventListener("unhandledrejection", errorHandler);
    };
  }, []);

  if (hasError) {
    return (
      <div style={styles.container}>
        <div style={styles.errorBox}>
          <h1 style={styles.title}>Oops! Something went wrong.</h1>
          <p style={styles.message}>
            We're sorry for the inconvenience. Please try again later or contact support if the issue persists.
          </p>
          <Button type="primary" style={styles.button}>
            <a href="/profile">GO BACK</a>
          </Button>
        </div>
      </div>
    );
  }

  return children;
};

const styles = {
  container: {
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    height: '100vh',
    backgroundColor: '#f0f2f5',
  },
  errorBox: {
    textAlign: 'center',
    backgroundColor: '#fff',
    padding: '40px',
    borderRadius: '8px',
    boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)',
  },
  title: {
    fontSize: '24px',
    color: '#ff4d4f',
  },
  message: {
    fontSize: '16px',
    color: '#595959',
    marginBottom: '20px',
  },
  button: {
    marginTop: '20px',
  },
};

export default ErrorBoundary;

you can use this functional based ERROR BOUNDARY Component

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.