React error page

Whenever an error occurs and an exception is thrown in a React application, there is a strong possibility that the application display no longer works and that the user will only see a blank page. To avoid this behavior, React introduced so-called Error Boundaries in version 16.0.

Whenever an error occurs and an exception is thrown in a React application, there is a strong possibility that the application display no longer works and that the user will only see a blank page. To avoid this behavior, React introduced so-called Error Boundaries in version 16.0.

An Error Boundary describes a component which can catch certain errors in its children and can also render an alternative component tree to protect users from experiencing a blank page. Error Boundaries always serve as a parent component of a component tree. If an exception is thrown in the component tree, the Error Boundary can intercept and handle the error. Try and think of error boundaries as a special form of a try / catch block for component hierarchies.

They can deal with mistakes that result from the handling of the following situations:

  • Errors in lifecycle methods

  • Errors in the render() method anywhere inside the Error Boundary

  • Errors in the constructor() of a component

If React encounters an error in a lifecycle method, the render() method or in the constructor of a component, the Error Boundary can safely prevent it. It can display a fallback that can prompt the user to restart their application or inform them that something has gone wrong. Similar to Context components, Error Boundaries can be nested inside each other. If an error occurs, the implementation of the higher Error Boundary component takes precedence.

Attention: Error Boundaries‘ primary goal is to prevent and deal with errors in the handling of user interfaces which would otherwise prevent further rendering of the application status. If you think about implementing form validation with Error Boundaries, please refrain from doing so as Error Boundaries were not intended for this use case and should not be used for that matter.

There are certain situations in which Error Boundaries do not work:

  • in asynchronous code (like setTimeOut() or requestAnimationFrame())

  • in server-side rendered components (SSR)

  • in errors which occur in the Error Boundary itself

Error Boundaries will not work in these situations as it is either not necessary or not possible for them to deal with the problem at hand. If an event-handler throws an error, this might not necessarily impact its render and React can continue to show a working interface to the user. The only repercussion would be the missing interaction based on said event.

Implementing an Error Boundary

There are two simple rules when it comes to implementing an Error Boundary:

  1. 1.

    Only Class components can be turned into an Error Boundary

  2. 2.

    The class has to implement the static getDerivedStateFromError() method or the class method componentDidCatch() (or both of them)

Strictly speaking, we are already dealing with an Error Boundary from a technical point of view if one or both of the methods mentioned above have been implemented. All other rules that apply to regular Class components also apply to Error Boundaries.

Let’s look at an implementation of an Error Boundary:

class ErrorBoundary extends React.Component {

static getDerivedStateFromError(error) {

componentDidCatch(error, info) {

console.log(error, info);

if (this.state.hasError) {

return <h1>An error has occured.</h1>;

return this.props.children;

First of all, we define a new component. We have named this component ErrorBoundary but it is possible to give it any other name too. You can freely choose the name of the Error Boundary and only need to adhere to React’s component naming conventions: components need to start with a capital letter and be a valid JavaScript function name.

For matters of simplicity and readability, I would urge you to choose clear and identifiable component names such asAppErrorBoundary or DataTableErrorFallback. This will allow other team members in your project to see which components are used to deal with errors at a glance.

In the above example we have set up state with a property of hasError and provided an initial value of false as errors usually do not occur during initialization.

Next, let’s look at the static getDerivedStateFromError() method. Using this method, React is informed that the component in use is supposed to act as an Error Boundary and should come into effect if an error occurs in its children. The method itself is passed an error object which is the same as the object which is also passed to the catch block of the try / catch statement.

getDerivedStateFromError() works very similar to the getDerivedStateFromProps() method we have already encountered in the chapter on lifecycle methods. It can return a new object and thus create new state or leave all as is by returning null. In the above example, we have set the hasError property to true and also save the error object in our state. As the method itself is static though, it cannot access other methods in the component.

This method is called during the render() phase of a component when React compares the current component tree with its previous version and just before the changes are committed to the DOM.

The componentDidCatch() method has also been implemented. It receives an error object as its first parameter and React-specific information as its second. This information contains the «Component Stack» — crucial information which allows us to trace in which components we have encountered errors and more specifically how which children and children of children were involved. It will display the component tree up until an error will occur. If you want to use an external service to log these errors, this method is a good place to deal with side effects. componentDidCatch() is run during the Commit phase meaning just after React has displayed changes from state in the DOM.

As componentDidCatch() is not a static method, it would be entirely possible to modify its state via this.setState(). However, the React Team plans to prohibit this usage in the future which is why I do not recommend it at this point. It is safer to use the static getDerivedStateFromError() method instead to create a new state and react to errors once they have occurred.

Finally, we react to possible errors in the render() method. If the hasError property in state is set to true, we know that an error has occurred and can thus display a warning such as <h1>An error occured.</h1>. If on the other hand everything works as expected, we simply return this.props.children. How exactly the errors encountered are dealt with is up to the developer. For example, it might be sufficient to inform the user that certain information cannot be displayed at the moment if the error is only small. If however serious errors have been encountered, we should prompt the user to reload the application.

Error Boundaries in practice

We now know how to implement an Error Boundary: by adding either static getDerivedStateFromError() or componentDidCatch() to your components. Error Boundaries should not implement their own logic, should not be too tightly coupled to other components and be as independent as possible. It is at the developer’s discretion to decide how granular the Error Boundary should be according to the specific use case.

It’s a good idea to implement different and nested Error Boundaries to cater to a variety of errors: one Error Boundary that wraps around the whole application, as well as one that wraps only optional components in the component tree. Let’s look at another example:

import React from ‘react’;

import ReactDOM from ‘react-dom’;

<ServiceUnavailableBoundary>

</ServiceUnavailableBoundary>

ReactDOM.render(<App />, document.querySelector(‘#root’));

Two Error Boundaries are used in the above example: ErrorBoundary and ServiceUnavailableBoundary. While the outer boundary will catch errors that might occur in the ApplicationLogic component, the ServiceUnavailableBoundary could catch errors in the weather widget and display a more granular error message like «the service requested cannot be reached at the moment. Please try again later».

If the WeatherWidget component throws an error, the ServiceUnavailableBoundary will catch it and everything that is currently used in the ApplicationLogic component will remain intact. If we did not include the WeatherWidget in its own Error Boundary, the outer Error Boundary would be used instead and the ApplicationLogic component would not be shown.

Generally, it is good practice to have at least one Error Boundary as high up as possible in the component hierarchy. This will catch most unexpected errors like a 500 Internal Server Error page would do and can also log them. If needed, further Error Boundaries should be added to encompass useful logic in further component trees. This depends entirely on how error prone a specific area of the tree is (due to unknown or changing data) or if a specific area of the tree has been neglected.

Since React version 16, components will be «unmounted» and removed from the tree if a serious error occurred or an exception was thrown. This is important as it ensures that the user interface does not suddenly stop working or returns incorrect data. It is especially critical to ensure if we were to work with online banking data. Imagine the consequences if we were to incorrectly send money to the wrong recipient or transfer an incorrect amount.

In order to deal with these errors and risks properly, Error Boundaries were introduced. They allow developers to inform users that the application is currently in an erroneous state. As errors and mistakes can never be fully avoided in an application, using Error Boundaries is highly recommended.

Abi Noda

Abi Noda

Posted on Jun 27, 2018

• Updated on Jul 3, 2018

If you like this article please support me by checking out Pull Reminders, a Slack bot that sends your team automatic reminders for GitHub pull requests.

One challenge I recently ran into while working with GraphQL and React was how to handle errors. As developers, we’ve likely implemented default 500, 404, and 403 pages in server-rendered applications before, but figuring out how to do this with React and GraphQL is tricky.

In this post, I’ll talk about how our team approached this problem, the final solution we implemented, and interesting lessons from the GraphQL spec.

Background

The project I was working on was a fairly typical CRUD app built in React using GraphQL, Apollo Client, and Express GraphQL. We wanted to handle certain types of errors — for example, the server being down — by displaying a standard error page to the user.

Our initial challenge was figuring out the best way to communicate errors to the client. GraphQL doesn’t use HTTP status codes like 500, 400, and 403. Instead, responses contain an errors array with a list of things that went wrong (read more about errors in the GraphQL spec).

For example, here’s what our GraphQL response looked like when something broke on the server:

{
  "errors": [
    {
      "message": "TypeError: Cannot read property 'name' of undefined",
      "locations": [
        {
          "line": 2,
          "column": 2
        }
      ],
      "path": [
        "program"
      ]
    }
  ],
  "data": {
    "program": null
  }
}

Enter fullscreen mode

Exit fullscreen mode

Since GraphQL error responses return HTTP status code 200, the only way to identify the kind of error was to inspect the errors array. This seemed like a poor approach because the error message was the message from the exception thrown on the server. The GraphQL spec states that the value of message is intended for developers, but it does not specify whether the value should be a human-readable message or something designed to be programmatically handled:

Every error must contain an entry with the key message with a string description of the error intended for the developer as a guide to understand and correct the error.

Adding Error Codes to GraphQL Responses

To solve for this, we added standardized error codes to our error objects, which could be used by clients to programmatically identify errors. This was inspired by how Stripe’s REST API returns string error codes in addition to human-readable messages.

We decided on three error codes to start: authentication_error, resource_not_found, and server_error.

To add these to our GraphQL responses, we passed our own formatError function to graphql-express that maps exceptions thrown on the server to standard codes which get added to the response. The GraphQL spec generally discourages adding properties to error objects, but does allow for it by nesting those entries in an extensions object.

const formatError = (error) => {
  const { constructor } = error.originalError;

  let code;

  switch (constructor) {
    case AuthorizationError:
      code = 'authorization_error';
    case ResourceNotFound:
      code = 'resource_not_found';
    default:
      code = 'server_error';
  }

  return {
    extensions: {
      code
    },
    ...error
  };
};

app.use('/graphql', (req, res) => graphqlHTTP({
  schema,
  graphiql: config.graphiql,
  context: { req, res },
  formatError
})(req, res));

Enter fullscreen mode

Exit fullscreen mode

Our GraphQL response errors were then easy to classify:

{
  "errors": [
    {
      "message": "TypeError: Cannot read property 'name' of undefined",
      "locations": [
        {
          "line": 2,
          "column": 2
        }
      ],
      "path": [
        "program"
      ],
      "extensions": {
        "code": "server_error"
      }
    }
  ],
  "data": {
    "program": null
  }
}

Enter fullscreen mode

Exit fullscreen mode

While we developed our own way of adding codes to responses generated by express-graphql, apollo-server appears to offer similar built-in behavior.

Rendering error pages with React Error Boundaries

Once we figured out a good way of handling errors in our server, we turned our attention to the client.

By default, we wanted our app to display a global error page (for example, a page with the message “oops something went wrong”) whenever we encountered a server_error, authorization_error, or authorization_not_found. However, we also wanted the flexibility to be able to handle an error in a specific component if we wanted to.

For example, if a user was typing something into a search bar and something went wrong, we wanted to display an error message in-context, rather than flash over to an error page.

To achieve this, we first created a component called GraphqlErrorHandler that would sit between apollo-client’s Query and Mutation components and their children to be rendered out. This component checked for error codes in the response threw an exception if it identified a code we cared about.

import React from 'react';
import {
  ServerError,
  AuthorizationError,
  ResourceNotFound
} from '../errors';

const checkFor = (code, errors) => errors && errors.find( e => e.extensions.code === code);

const checkError = ({ networkError, graphQLErrors }) => {
  // networkError is defined when the response is not a valid GraphQL response, e.g. the server is completely down
  if ( networkError ) {
    throw new ServerError();
  }

  if (checkFor('server_error', graphQLErrors)) {
    throw new ServerError();
  }

  if (checkFor('authorization_error', graphQLErrors)) {
    throw new AuthorizationError();
  }

  if (checkFor('resource_not_found', graphQLErrors)) {
    throw new ResourceNotFound();
  }
};

const GraphqlErrorHandler = ({ error, children }) => {
  if (error) checkError(error);
  return children;
};

export default GraphqlErrorHandler;

Enter fullscreen mode

Exit fullscreen mode

To use the GraphqlErrorHandler, we wrapped apollo-client’s Query and Mutation components:

import React from 'react';
import Query from 'Components/graphql/Query';
import GET_PROGRAM from './queries/getProgram';
import ViewProgram from './ViewProgram';

const ViewProgramContainer = (props) => {
  const { programCode } = props.match.params;

  return (
    <Query query={GET_PROGRAM} variables={{ programCode }}>
      {({ loading, data, }) => (
        <ViewProgram program={data.program} loading={loading} />
      )}
    </Query>
  );
};

export default ViewProgramContainer;

Enter fullscreen mode

Exit fullscreen mode

Now that our React app was throwing exceptions when the server returned errors, we wanted to handle these exceptions and map them to the appropriate behavior.

Remember from earlier that our goal was to default to displaying global error pages (for example, a page with the message “oops something went wrong”), but still have the flexibility to handle an error locally within any component if we desired.

React error boundaries provide a fantastic way of doing this. Error boundaries are React components that can catch JavaScript errors anywhere in their child component tree so you can handle them with custom behavior.

We created an error boundary called GraphqlErrorBoundary that would catch any server-related exceptions and display the appropriate error page:

import React from 'react';
import ServerErrorPage from 'Components/errors/ServerError';
import NotFoundPageErrorPage from 'Components/errors/NotFound';
import UnauthorizedErrorPage from 'Components/errors/Unauthorized';
import {
  ServerError,
  AbsenceError,
  AuthorizationError,
  ResourceNotFound
} from '../errors';

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

  componentDidCatch(error) {
    if ( error.name === AuthorizationError.name ) {
      this.setState({ error: AuthorizationError.name });
    } else if ( error.name === ServerError.name ) {
      this.setState({ error: ServerError.name });
    } else if ( error.name === ResourceNotFound.name ) {
      this.setState({ error: ResourceNotFound.name });
    } else {
      this.setState({ error: ServerError.name });
    }
  }

  render() {
    if (this.state.error === ServerError.name ) {
      return <ServerErrorPage />
    } else if (this.state.error === AuthorizationError.name) {
      return <UnauthorizedErrorPage />
    } else if (this.state.error === ResourceNotFound.name) {
      return <NotFoundErrorPage />
    }
    return this.props.children;
  }
}

export default GraphqlErrorBoundary;

Enter fullscreen mode

Exit fullscreen mode

We then wrapped our app’s components with this error boundary:

const App = () => {
  return (
    <div className='appContainer'>
      <Header />
      <GraphqlErrorBoundary>
        <Routes />
      </GraphqlErrorBoundary>
      <Footer />
    </div>
  );
};

Enter fullscreen mode

Exit fullscreen mode

If we wanted to handle errors within a specific component instead of rendering an error page, we could turn that component into an error boundary. For example, here’s what it’d look if we wanted custom error handling behavior in our component from earlier:

import React from 'react';
import Query from 'Components/graphql/Query';
import GET_PROGRAM from './queries/getProgram';
import ViewProgram from './ViewProgram';

class ViewProgramContainer extends React.Component {
  componentDidCatch(error) {
    if (error.name === ServerError.name) {
      // do something
    }
  }

  render() {
    const { programCode } = this.props.match.params;

    return (
      <Query query={GET_PROGRAM} variables={{ programCode }}>
        {({ loading, data, }) => (
          <ViewProgram program={data.program} loading={loading} />
        )}
      </Query>
    );
  }
}

export default ViewProgramContainer;

Enter fullscreen mode

Exit fullscreen mode

Wrap up

GraphQL is still relatively new, and error handling is a common challenge that developers seem to be running into. By using standardized error codes in our GraphQL responses, we can communicate errors to clients in a useful and intuitive way. In our React apps, error boundaries provide a great way to standardize our app’s error handling behavior while still having flexibility when we need it.

Custom error pages in React with GraphQL and Error Boundaries

by Abi Noda

1*20J63XycbuDOp4NRTLKeMQ

GitHub’s awesome 500 error page

If you like this article please support me by checking out Pull Reminders, a Slack bot that sends your team automatic reminders for GitHub pull requests.

One challenge I recently ran into while working with GraphQL and React was how to handle errors. As developers, we’ve likely implemented default 500, 404, and 403 pages in server-rendered applications before, but figuring out how to do this with React and GraphQL is tricky.

In this post, I’ll talk about how our team approached this problem, the final solution we implemented, and interesting lessons from the GraphQL spec.

Background

The project I was working on was a fairly typical CRUD app built in React using GraphQL, Apollo Client, and express-graphQL. We wanted to handle certain types of errors — for example, the server being down — by displaying a standard error page to the user.

Our initial challenge was figuring out the best way to communicate errors to the client. GraphQL doesn’t use HTTP status codes like 500, 400, and 403. Instead, responses contain an errors array with a list of things that went wrong (read more about errors in the GraphQL spec).

For example, here’s what our GraphQL response looked like when something broke on the server:

Since GraphQL error responses return HTTP status code 200, the only way to identify the kind of error was to inspect the errors array. This seemed like a poor approach because the error message property contained the exception thrown on the server. The GraphQL spec states that the value of message is intended for developers, but it does not specify whether the value should be a human-readable message or something designed to be programmatically handled:

Every error must contain an entry with the key message with a string description of the error intended for the developer as a guide to understand and correct the error.

Adding Error Codes to GraphQL Responses

To solve for this, we added standardized error codes to our error objects, which could be used by clients to programmatically identify errors. This was inspired by how Stripe’s REST API returns string error codes in addition to human-readable messages.

We decided on three error codes to start: authentication_error, resource_not_found, and server_error.

To add these to our GraphQL responses, we passed our own formatError function to express-graphql that maps exceptions thrown on the server to standard codes which get added to the response. The GraphQL spec generally discourages adding properties to error objects, but does allow for it by nesting those entries in a extensions object.

Our GraphQL response errors were then easy to classify:

While we developed our own way of adding codes to responses generated by express-graphql, apollo-server appears to offer similar built-in behavior.

Rendering error pages with React Error Boundaries

Once we figured out a good way of handling errors in our server, we turned our attention to the client.

By default, we wanted our app to display a global error page (for example, a page with the message “oops something went wrong”) whenever we encountered a server_error, authorization_error, or authorization_not_found. However, we also wanted the flexibility to be able to handle an error in a specific component if we wanted to.

For example, if a user was typing something into a search bar and something went wrong, we wanted to display an error message in-context, rather than flash over to an error page.

To achieve this, we first created a component called GraphqlErrorHandler that would sit between apollo-client’s Query and Mutation components and their children to be rendered out. This component checked for error codes in the response threw an exception if it identified a code we cared about:

To use the GraphqlErrorHandler, we wrapped apollo-client’s Query and Mutation components:

Our feature component then used our own Query component instead of directly accessing react-apollo:

Now that our React app was throwing exceptions when the server returned errors, we wanted to handle these exceptions and map them to the appropriate behavior.

Remember from earlier that our goal was to default to displaying global error pages (for example, a page with the message “oops something went wrong”), but still have the flexibility to handle an error locally within any component if we desired.

React error boundaries provide a fantastic way of doing this. Error boundaries are React components that can catch JavaScript errors anywhere in their child component tree so you can handle them with custom behavior.

We created an error boundary called GraphqlErrorBoundary that would catch any server-related exceptions and display the appropriate error page:

We use the error boundary as a wrapper for all of our app’s components:

Error boundaries can be used deeper in the component tree if we want to handle errors in a component instead of rendering an error page.

For example, here’s what it’d look if we wanted custom error handling behavior in our component from earlier:

Wrap-up

GraphQL is still relatively new, and error handling is a common challenge that developers seem to be running into. By using standardized error codes in our GraphQL responses, we can communicate errors to clients in a useful and intuitive way. In our React apps, error boundaries provide a great way to standardize our app’s error handling behavior while still having flexibility when we need it.


Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started

react-error-boundary

Simple reusable React error boundary component


Build Status
Code Coverage
version
downloads
MIT License
PRs Welcome
Code of Conduct

The problem

React v16 introduced the
concept of “error boundaries”.

This solution

This component provides a simple and reusable wrapper that you can use to wrap
around your components. Any rendering errors in your components hierarchy can
then be gracefully handled.

Reading this blog post will help you understand what react-error-boundary does
for you:
Use react-error-boundary to handle errors in React
– How to simplify your React apps by handling React errors effectively with
react-error-boundary

Table of Contents

  • Installation
  • Usage
    • Error Recovery
  • API
    • ErrorBoundary props
    • useErrorHandler(error?: unknown)
  • Issues
    • 🐛 Bugs
    • 💡 Feature Requests
  • LICENSE

Installation

This module is distributed via npm which is bundled with node and
should be installed as one of your project’s dependencies:

npm install --save react-error-boundary

Usage

The simplest way to use <ErrorBoundary> is to wrap it around any component
that may throw an error. This will handle errors thrown by that component and
its descendants too.

import {ErrorBoundary} from 'react-error-boundary'

function ErrorFallback({error, resetErrorBoundary}) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  )
}

const ui = (
  <ErrorBoundary
    FallbackComponent={ErrorFallback}
    onReset={() => {
      // reset the state of your app so the error doesn't happen again
    }}
  >
    <ComponentThatMayError />
  </ErrorBoundary>
)

You can react to errors (e.g. for logging) by providing an onError callback:

import {ErrorBoundary} from 'react-error-boundary'

const myErrorHandler = (error: Error, info: {componentStack: string}) => {
  // Do something with the error
  // E.g. log to an error logging client here
}

const ui = (
  <ErrorBoundary FallbackComponent={ErrorFallback} onError={myErrorHandler}>
    <ComponentThatMayError />
  </ErrorBoundary>,
)

You can also use it as a
higher-order component:

import {withErrorBoundary} from 'react-error-boundary'

const ComponentWithErrorBoundary = withErrorBoundary(ComponentThatMayError, {
  FallbackComponent: ErrorBoundaryFallbackComponent,
  onError(error, info) {
    // Do something with the error
    // E.g. log to an error logging client here
  },
})

const ui = <ComponentWithErrorBoundary />

Error Recovery

In the event of an error if you want to recover from that error and allow the
user to «try again» or continue with their work, you’ll need a way to reset the
ErrorBoundary’s internal state. You can do this various ways, but here’s the
most idiomatic approach:

function ErrorFallback({error, resetErrorBoundary}) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  )
}

function Bomb() {
  throw new Error('💥 CABOOM 💥')
}

function App() {
  const [explode, setExplode] = React.useState(false)
  return (
    <div>
      <button onClick={() => setExplode(e => !e)}>toggle explode</button>
      <ErrorBoundary
        FallbackComponent={ErrorFallback}
        onReset={() => setExplode(false)}
        resetKeys={[explode]}
      >
        {explode ? <Bomb /> : null}
      </ErrorBoundary>
    </div>
  )
}

So, with this setup, you’ve got a button which when clicked will trigger an
error. Clicking the button again will trigger a re-render which recovers from
the error (we no longer render the <Bomb />). We also pass the resetKeys
prop which is an array of elements for the ErrorBoundary to check each render
(if there’s currently an error state). If any of those elements change between
renders, then the ErrorBoundary will reset the state which will re-render the
children.

We have the onReset prop so that if the user clicks the «Try again» button we
have an opportunity to re-initialize our state into a good place before
attempting to re-render the children.

This combination allows us both the opportunity to give the user something
specific to do to recover from the error, and recover from the error by
interacting with other areas of the app that might fix things for us. It’s hard
to describe here, but hopefully it makes sense when you apply it to your
specific scenario.

API

ErrorBoundary props

children

This is what you want rendered when everything’s working fine. If there’s an
error that React can handle within the children of the ErrorBoundary, the
ErrorBoundary will catch that and allow you to handle it gracefully.

FallbackComponent

This is a component you want rendered in the event of an error. As props it will
be passed the error and resetErrorBoundary (which will reset the error
boundary’s state when called, useful for a «try again» button when used in
combination with the onReset prop).

This is required if no fallback or fallbackRender prop is provided.

fallbackRender

This is a render-prop based API that allows you to inline your error fallback UI
into the component that’s using the ErrorBoundary. This is useful if you need
access to something that’s in the scope of the component you’re using.

It will be called with an object that has error and resetErrorBoundary:

const ui = (
  <ErrorBoundary
    fallbackRender={({error, resetErrorBoundary}) => (
      <div role="alert">
        <div>Oh no</div>
        <pre>{error.message}</pre>
        <button
          onClick={() => {
            // this next line is why the fallbackRender is useful
            resetComponentState()
            // though you could accomplish this with a combination
            // of the FallbackCallback and onReset props as well.
            resetErrorBoundary()
          }}
        >
          Try again
        </button>
      </div>
    )}
  >
    <ComponentThatMayError />
  </ErrorBoundary>
)

I know what you’re thinking: I thought we ditched render props when hooks came
around. Unfortunately, the current React Error Boundary API only supports class
components at the moment, so render props are the best solution we have to this
problem.

This is required if no FallbackComponent or fallback prop is provided.

fallback

In the spirit of consistency with the React.Suspense component, we also
support a simple fallback prop which you can use for a generic fallback. This
will not be passed any props so you can’t show the user anything actually useful
though, so it’s not really recommended.

const ui = (
  <ErrorBoundary fallback={<div>Oh no</div>}>
    <ComponentThatMayError />
  </ErrorBoundary>
)

onError

This will be called when there’s been an error that the ErrorBoundary has
handled. It will be called with two arguments: error, info.

onReset

This will be called immediately before the ErrorBoundary resets it’s internal
state (which will result in rendering the children again). You should use this
to ensure that re-rendering the children will not result in a repeat of the same
error happening again.

onReset will be called with whatever resetErrorBoundary is called with.

Important: onReset will not be called when reset happens from a change
in resetKeys. Use onResetKeysChange for that.

resetKeys

Sometimes an error happens as a result of local state to the component that’s
rendering the error. If this is the case, then you can pass resetKeys which is
an array of values. If the ErrorBoundary is in an error state, then it will
check these values each render and if they change from one render to the next,
then it will reset automatically (triggering a re-render of the children).

See the recovery examples above.

onResetKeysChange

This is called when the resetKeys are changed (triggering a reset of the
ErrorBoundary). It’s called with the prevResetKeys and the resetKeys.

useErrorHandler(error?: unknown)

React’s error boundaries feature is limited in that the boundaries can only
handle errors thrown during React’s lifecycles. To quote
the React docs on Error Boundaries:

Error boundaries do not catch errors for:

  • Event handlers
    (learn more)
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server side rendering
  • Errors thrown in the error boundary itself (rather than its children)

This means you have to handle those errors yourself, but you probably would like
to reuse the error boundaries you worked hard on creating for those kinds of
errors as well. This is what useErrorHandler is for.

There are two ways to use useErrorHandler:

  1. const handleError = useErrorHandler(): call handleError(theError)
  2. useErrorHandler(error): useful if you are managing the error state yourself
    or get it from another hook.

Here’s an example:

import { useErrorHandler } from 'react-error-boundary'

function Greeting() {
  const [greeting, setGreeting] = React.useState(null)
  const handleError = useErrorHandler()

  function handleSubmit(event) {
    event.preventDefault()
    const name = event.target.elements.name.value
    fetchGreeting(name).then(
      newGreeting => setGreeting(newGreeting),
      handleError,
    )
  }

  return greeting ? (
    <div>{greeting}</div>
  ) : (
    <form onSubmit={handleSubmit}>
      <label>Name</label>
      <input id="name" />
      <button type="submit">get a greeting</button>
    </form>
  )
}

Note, in case it’s not clear what’s happening here, you could also write
handleSubmit like this:

function handleSubmit(event) {
  event.preventDefault()
  const name = event.target.elements.name.value
  fetchGreeting(name).then(
    newGreeting => setGreeting(newGreeting),
    error => handleError(error),
  )
}

Alternatively, let’s say you’re using a hook that gives you the error:

import { useErrorHandler } from 'react-error-boundary'

function Greeting() {
  const [name, setName] = React.useState('')
  const {greeting, error} = useGreeting(name)
  useErrorHandler(error)

  function handleSubmit(event) {
    event.preventDefault()
    const name = event.target.elements.name.value
    setName(name)
  }

  return greeting ? (
    <div>{greeting}</div>
  ) : (
    <form onSubmit={handleSubmit}>
      <label>Name</label>
      <input id="name" />
      <button type="submit">get a greeting</button>
    </form>
  )
}

In this case, if the error is ever set to a truthy value, then it will be
propagated to the nearest error boundary.

In either case, you could handle those errors like this:

const ui = (
  <ErrorBoundary FallbackComponent={ErrorFallback}>
    <Greeting />
  </ErrorBoundary>
)

And now that’ll handle your runtime errors as well as the async errors in the
fetchGreeting or useGreeting code.

Issues

Looking to contribute? Look for the Good First Issue
label.

🐛 Bugs

Please file an issue for bugs, missing documentation, or unexpected behavior.

See Bugs

💡 Feature Requests

Please file an issue to suggest new features. Vote on feature requests by adding
a 👍. This helps maintainers prioritize what to work on.

See Feature Requests

LICENSE

MIT

Let’s face it. Nobody wants to see a broken, empty page while surfing the web. It leaves you stranded and confused. You don’t know what happened or what caused it, leaving you with a bad impression of the website.

It is often better to communicate the error and let the user continue to use the app. The user will get less of a bad impression and can continue to use its features.

In today’s post, we’ll go through different ways to handle errors in React applications.

The Classic ‘Try and Catch’ Method in React

If you’ve used JavaScript, you’ve probably had to write a ‘try and catch’ statement. To make sure we’re on board with what it is, here’s one:

try {
  somethingBadMightHappen();
} catch (error) {
  console.error("Something bad happened");
  console.error(error);
}

It is a great tool to catch misbehaving code and ensure our app doesn’t blow up into smithereens. To be more realistic and close to the React world as possible, let’s see an example of how you’d use this in your app:

const fetchData = async () => {
  try {
    return await fetch("https://some-url-that-might-fail.com");
  } catch (error) {
    console.error(error); // You might send an exception to your error tracker like AppSignal
    return error;
  }
};

When doing network calls in React, you’d usually use the try...catch statement. But why? Unfortunately, try...catch only works on imperative code. It does not work on declarative code like the JSX we are writing in our components. So that is why you don’t see a massive try...catch wrapping our whole app. It just won’t work.

So, what do we do? Glad you asked. In React 16, a new concept got introduced — React Error Boundaries. Let’s dig into what they are.

React Error Boundaries

Before we get into error boundaries, let us first see why they are necessary. Imagine you had a component like this:

const CrashableComponent = (props) => {
  return <span>{props.iDontExist.prop}</span>;
};
 
export default CrashableComponent;

If you try to render this component somewhere, you’ll get an error like this one:

Crashable component renders error in the console

Not only that, the whole page will be blank, and the user won’t be able to do or see anything. But what happened? We tried to access a property iDontExist.prop, which doesn’t exist (we don’t pass it to the component). This is a banal example, but it shows that we cannot catch these errors with the try...catch statement.

This whole experiment brings us to error boundaries. Error boundaries are React components that catch JavaScript errors anywhere in their child component tree. Then, they log those caught errors and display a fallback UI instead of the component tree that crashed. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them.

An error boundary is a class component that defines either (or both) of the lifecycle methods static getDerivedStateFromError() or componentDidCatch().
static getDerivedStateFromError() renders a fallback UI after an error has been thrown.
componentDidCatch() can log error information to your service provider (like AppSignal) or to a browser console.

Here’s an example of how information about a React error looks in AppSignal’s ‘issue list’:

React error

Let’s see a typical error boundary component:

import { Component } from "react";
 
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
 
  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return {
      hasError: true,
      error,
    };
  }
 
  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service like AppSignal
    // logErrorToMyService(error, errorInfo);
  }
 
  render() {
    const { hasError, error } = this.state;
 
    if (hasError) {
      // You can render any custom fallback UI
      return (
        <div>
          <p>Something went wrong 😭</p>
 
          {error.message && <span>Here's the error: {error.message}</span>}
        </div>
      );
    }
 
    return this.props.children;
  }
}
 
export default ErrorBoundary;

We can use ErrorBoundary like so:

<ErrorBoundary>
  <CrashableComponent />
</ErrorBoundary>

Now, when we open our app, we will get a working app with the following:

Error boundary shows the error

That is precisely what we want. We want our app to remain functional when an error occurs. But we also want to inform the user (and our error tracking service) about the error.

Beware that using an error boundary is not a silver bullet. Error boundaries do not catch errors for:

  • Event handlers
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)
  • Server-side rendering
  • Errors that are thrown in the error boundary itself (rather than its children)

You still need to use the try...catch statement for these fellas. So, let’s go ahead and show how you can do that.

Error Catching in Event Handlers

As mentioned before, error boundaries can’t help us when an error is thrown inside an event handler. Let’s see how we can handle those. Below is a small button component that throws an error when you click it:

import { useState } from "react";
 
const CrashableButton = () => {
  const [error, setError] = useState(null);
 
  const handleClick = () => {
    try {
      throw Error("Oh no :(");
    } catch (error) {
      setError(error);
    }
  };
 
  if (error) {
    return <span>Caught an error.</span>;
  }
 
  return <button onClick={handleClick}>Click Me To Throw Error</button>;
};
 
export default CrashableButton;

Notice that we have a try and catch block inside handleClick that ensures our error is caught. If you render the component and try to click it, this happens:

Clicking a button catches an error and displays error text

We have to do the same in other cases, like in setTimeout calls.

Error Catching in setTimeout Calls

Imagine we have a similar button component, but this one calls setTimeout when clicked. Here’s how it looks:

import { useState } from "react";
 
const SetTimeoutButton = () => {
  const [error, setError] = useState(null);
 
  const handleClick = () => {
    setTimeout(() => {
      try {
        throw Error("Oh no, an error :(");
      } catch (error) {
        setError(error);
      }
    }, 1000);
  };
 
  if (error) {
    return <span>Caught a delayed error.</span>;
  }
 
  return (
    <button onClick={handleClick}>Click Me To Throw a Delayed Error</button>
  );
};
 
export default SetTimeoutButton;

After 1,000 milliseconds, the setTimeout callback will throw an error. Luckily, we wrap that callback logic in try...catch, and setError in the component. That way, no stack trace is shown in the browser console. Also, we communicate the error to the user. Here’s how it looks in the app:

Clicking a button causes a delayed error that gets caught

That is all well and good, as we got our app’s pages up and running despite errors popping all over the place in the background. But is there an easier way to handle errors without writing custom error boundaries? You bet there is, and of course, it comes in the form of a JavaScript package. Let me introduce you to the react-error-boundary.

JavaScript’s react-error-boundary Package

You can pop that library inside your package.json faster than ever with:

npm install --save react-error-boundary

Now, you’re ready to use it. Remember the ErrorBoundary component we made? You can forget about it because this package exports its own. Here’s how to use it:

import { ErrorBoundary } from "react-error-boundary";
import CrashableComponent from "./CrashableComponent";
 
const FancyDependencyErrorHandling = () => {
  return (
    <ErrorBoundary
      FallbackComponent={ErrorFallback}
      onError={(error) => {
        // You can also log the error to an error reporting service like AppSignal
        // logErrorToMyService(error, errorInfo);
        console.error(error);
      }}
    >
      <CrashableComponent />
    </ErrorBoundary>
  );
};
 
const ErrorFallback = ({ error }) => (
  <div>
    <p>Something went wrong 😭</p>
 
    {error.message && <span>Here's the error: {error.message}</span>}
  </div>
);
 
export default FancyDependencyErrorHandling;

In the example above, we render the same CrashableComponent, but this time, we use the ErrorBoundary component from the react-error-boundary library. It does the same thing as our custom one, except that it receives the FallbackComponent prop plus the onError function handler. The result is the same as we had with our custom ErrorBoundary component, except you don’t have to worry about maintaining it since you’re using an external package.

One great thing about this package is that you can easily wrap your function components into a withErrorBoundary making it a higher-order component (HOC). Here’s how that looks:

import { withErrorBoundary } from "react-error-boundary";
 
const CrashableComponent = (props) => {
  return <span>{props.iDontExist.prop}</span>;
};
 
export default withErrorBoundary(CrashableComponent, {
  FallbackComponent: () => <span>Oh no :(</span>,
});

Nice, you’re good to go now to capture all those errors bugging you.

But maybe you don’t want another dependency in your project. Can you achieve it yourself? Of course you can. Let’s see how it can be done.

Using Your Own React Boundaries

You can achieve a similar, if not the same, effect you get from react-error-boundary. We already showed a custom ErrorBoundary component, but let’s improve it.

import { Component } from "react";
 
export default class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }
 
  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return {
      hasError: true,
      error,
    };
  }
 
  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service like AppSignal
    // logErrorToMyService(error, errorInfo);
  }
 
  render() {
    const { hasError, error } = this.state;
 
    if (hasError) {
      // You can render any custom fallback UI
      return <ErrorFallback error={error} />;
    }
 
    return this.props.children;
  }
}
 
const ErrorFallback = ({ error }) => (
  <div>
    <p>Something went wrong 😭</p>
 
    {error.message && <span>Here's the error: {error.message}</span>}
  </div>
);
 
const errorBoundary = (WrappedComponent) => {
  return class extends ErrorBoundary {
    render() {
      const { hasError, error } = this.state;
 
      if (hasError) {
        // You can render any custom fallback UI
        return <ErrorFallback error={error} />;
      }
 
      return <WrappedComponent {...this.props} />;
    }
  };
};
 
export { errorBoundary };

Now you get the ErrorBoundary and the HOC errorBoundary that you can use across your app. Extend and play around with it as much as you want. You can make them receive custom fallback components to customize how you recover from each error. You can also make them receive an onError prop and later call it inside componentDidCatch. The possibilities are endless.

But one thing is for sure — you didn’t need that dependency after all. I bet writing your own error boundary will bring a sense of achievement, and you’ll get to understand it better. Also, who knows what ideas you might get when you’re trying to customize it.

Summing Up: Get Started with React Error Handling

Thanks for reading this blog post about handling errors in React. I hope you had as much fun reading and trying things out as I did writing it. You can find all the code, with examples, in the GitHub repo I created.

A quick rundown of the things we went through:

  • React Error boundaries are great for catching errors in declarative code (e.g., inside their child component tree).
  • For other cases, you need to use a try...catch statement (e.g., async calls like setTimeout, event handlers, server-side rendering, and errors thrown in the error boundary itself).
  • A library like react-error-boundary can help you write less code.
  • You can also run your own error boundary and customize it as much as you want.

That is all, folks. Thanks for tuning in, and catch you in the next one!

P.S. If you liked this post, subscribe to our JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.

P.P.S. If you need an APM for your Node.js app, go and check out the AppSignal APM for Node.js.

Понравилась статья? Поделить с друзьями:
  • React error boundary npm
  • React error boundary hooks
  • React error boundary hook
  • React error boundary functional components
  • React consider adding an error boundary to your tree to customize error handling behavior