Use error boundary

React hook for using error boundaries in your functional components - GitHub - JoschuaSchneider/use-error-boundary: React hook for using error boundaries in your functional components

use-error-boundary

npm version
TypeScript
build workflow
test workflow
license

A react hook for using error boundaries in your functional components.

It lets you keep track of the error state of child components, by wrapping them in the provided ErrorBoundary component.

⚠️ Read more about error boundaries and their intended use in the React documentation, this will only catch errors during the render phase!

Installation

yarn add use-error-boundary

Breaking changes in 2.x

While upgrading from version 1.x make sure you are not using the errorInfo object.
The hook and the renderError callback no longer provide this object.

For advanced use, please refer to Custom handling of error and errorInfo.

Examples and usage

Import the hook:

// Named
import { useErrorBoundary } from "use-error-boundary"
// Default
import useErrorBoundary from "use-error-boundary"

Learn more about the properties that are returned.

const MyComponent = () => {

  const {
    ErrorBoundary,
    didCatch,
    error,
    reset
  } = useErrorBoundary()

  //...

}

Usage without render props

Wrap your components in the provided ErrorBoundary.
When it catches an error the hook provides you the changed error-state and the boundary Component will render nothing.
You have to handle rendering some error display yourself.

You can get the ErrorBoundary component to render your custom error display by using the renderError render-prop.

const JustRenderMe = () => {
  throw new Error("💥")
}

const MyComponent = () => {
  const { ErrorBoundary, didCatch, error } = useErrorBoundary()

  return (
    <>
      {didCatch ? (
        <p>An error has been caught: {error.message}</p>
      ) : (
        <ErrorBoundary>
          <JustRenderMe />
        </ErrorBoundary>
      )}
    </>
  )
}

Usage with render props

Optionally, you can pass a render and renderError function to render your UI and error-state inside the boundary.

/**
 * The renderError function also passes the error, so that you can display it using
 * render props.
 */
return (
  <ErrorBoundary
    render={() => <SomeChild />}
    renderError={({ error }) => <MyErrorComponent error={error} />}
  />
)

Handling error and errorInfo outside of markup

The hook now accepts an options object that you can pass a onDidCatch callback that gets called when the ErrorBoundary catches an error. Use this for logging or reporting of errors.

useErrorBoundary({
  onDidCatch: (error, errorInfo) => {
    // For logging/reporting
  },
})

Returned Properties

These are the properties of the returned Object:

ErrorBoundary

Type: React Component

Special error boundary component that provides state changes to the hook.

⚠️ You need to use this as the error boundary! Otherwise, the state will not update when errors are caught!

The ErrorBoundary is guaranteed referential equality across rerenders and only updates after a reset.

didCatch

Type: boolean

The error state, true if an error has ben caught.

error

Type: any | null

The error caught by the boundary, or null.

reset

Type: function

Function the reset the error state.
Forces react to recreate the boundary by creating a new ErrorBoundary

Your boundary can now catch errors again.

If you are searching for the errorInfo property, please read Breaking Changes in 2.x.

Why should I use this hook?

React does not provide a way to catch errors within the same functional component and you have to handle that in a class Component with special lifecycle methods.

If you are new to ErrorBoundaries, building this yourself is a good way to get started!

This packages purpose is to provide an easy drop in replacement for projects that are being migrated to hooks.

This also pulls the error presentation out of the error boundary, and on the same level you are handling errors.

Contributing

Contributions are always welcome.

Feel free to open issues or pull requests!

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.

What’s wrong with this code?

import * as React from 'react'
import ReactDOM from 'react-dom'

function Greeting({subject}) {
  return <div>Hello {subject.toUpperCase()}</div>
}

function Farewell({subject}) {
  return <div>Goodbye {subject.toUpperCase()}</div>
}

function App() {
  return (
    <div>
      <Greeting />
      <Farewell />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

If you send that to production, your users are going to get the white screen of
sadness:

Chrome window with nothing but white

If you run this with create-react-app’s error overlay (during development),
you’ll get this:

TypeError Cannot read property 'toUpperCase' of undefined

The problem is we need to either pass a subject prop (as a string) or default
the subject prop’s value. Obviously, this is contrived, but runtime errors
happen all of the time and that’s why it’s a good idea to gracefully handle such
errors. So let’s leave this error in for a moment and see what tools React has
for us to handle runtime errors like this.

try/catch?

The naive approach to handling this kind of error would be to add a try/catch:

import * as React from 'react'
import ReactDOM from 'react-dom'

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

function Greeting({subject}) {
  try {
    return <div>Hello {subject.toUpperCase()}</div>
  } catch (error) {
    return <ErrorFallback error={error} />
  }
}

function Farewell({subject}) {
  try {
    return <div>Goodbye {subject.toUpperCase()}</div>
  } catch (error) {
    return <ErrorFallback error={error} />
  }
}

function App() {
  return (
    <div>
      <Greeting />
      <Farewell />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

That «works»:

But, it may be ridiculous of me, but what if I don’t want to wrap every
component in my app in a try/catch block? In regular JavaScript, you can
simply wrap the calling function in a try/catch and it’ll catch any errors in
the functions it calls. Let’s try that here:

import * as React from 'react'
import ReactDOM from 'react-dom'

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

function Greeting({subject}) {
  return <div>Hello {subject.toUpperCase()}</div>
}

function Farewell({subject}) {
  return <div>Goodbye {subject.toUpperCase()}</div>
}

function App() {
  try {
    return (
      <div>
        <Greeting />
        <Farewell />
      </div>
    )
  } catch (error) {
    return <ErrorFallback error={error} />
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

Unfortunately, this doesn’t work. And that’s because we’re not the ones calling
Greeting and Farewell. React calls those functions. When we use them in JSX,
we’re simply creating React elements with those functions as the type. Telling
React that «if the App is rendered, here are the other components that will
need to be called.» But we’re not actually calling them, so the try/catch
won’t work.

I’m not too disappointed by this to be honest, because try/catch is inherently
imperative and I’d prefer a declarative way to handle errors in my app anyway.

React Error Boundary

This is where the
Error Boundary feature comes
in to play. An «Error Boundary» is a special component that you write to handle
runtime errors like those above. For a component to be an Error Boundary:

  1. It must be a class component 🙁
  2. It must implement either getDerivedStateFromError or componentDidCatch.

Luckily, we have react-error-boundary
which exposes the last Error Boundary component anyone needs to write because it
gives you all the tools you need to declaratively handle runtime errors in your
React apps.

So let’s add react-error-boundary and
render the ErrorBoundary component:

import * as React from 'react'
import ReactDOM from 'react-dom'
import {ErrorBoundary} from 'react-error-boundary'

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

function Greeting({subject}) {
  return <div>Hello {subject.toUpperCase()}</div>
}

function Farewell({subject}) {
  return <div>Goodbye {subject.toUpperCase()}</div>
}

function App() {
  return (
    <div>
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <Greeting />
        <Farewell />
      </ErrorBoundary>
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

And that works perfectly:

Error Recovery

The nice thing about this is you can almost think about the ErrorBoundary
component the same way you do a try/catch block. You can wrap it around a
bunch of React components to handle lots of errors, or you can scope it down to
a specific part of the tree to have more granular error handling and recovery.
react-error-boundary gives us all the
tools we need to manage this as well.

Here’s a more complex example:

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

function Bomb({username}) {
  if (username === 'bomb') {
    throw new Error('💥 CABOOM 💥')
  }
  return `Hi ${username}`
}

function App() {
  const [username, setUsername] = React.useState('')
  const usernameRef = React.useRef(null)

  return (
    <div>
      <label>
        {`Username (don't type "bomb"): `}
        <input
          placeholder={`type "bomb"`}
          value={username}
          onChange={e => setUsername(e.target.value)}
          ref={usernameRef}
        />
      </label>
      <div>
        <ErrorBoundary
          FallbackComponent={ErrorFallback}
          onReset={() => {
            setUsername('')
            usernameRef.current.focus()
          }}
          resetKeys={[username]}
        >
          <Bomb username={username} />
        </ErrorBoundary>
      </div>
    </div>
  )
}

Here’s what that experience is like:

Username (don’t type «bomb»):

Hi

You’ll notice that if you type «bomb», the Bomb component is replaced by the
ErrorFallback component and you can recover by either changing the username
(because that’s in the resetKeys prop) or by clicking «Try again» because
that’s wired up to resetErrorBoundary and we have an onReset that resets our
state to a username that won’t trigger the error all over again.

Handle all errors

Unfortunately, there are some errors that React doesn’t/can’t hand off to our
Error Boundary. To quote
the React docs:

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)

Most of the time, folks will manage some error state and render something
different in the event of an error, like so:

function Greeting() {
  const [{status, greeting, error}, setState] = React.useState({
    status: 'idle',
    greeting: null,
    error: null,
  })

  function handleSubmit(event) {
    event.preventDefault()
    const name = event.target.elements.name.value
    setState({status: 'pending'})
    fetchGreeting(name).then(
      newGreeting => setState({greeting: newGreeting, status: 'resolved'}),
      newError => setState({error: newError, status: 'rejected'}),
    )
  }

  return status === 'rejected' ? (
    <ErrorFallback error={error} />
  ) : status === 'resolved' ? (
    <div>{greeting}</div>
  ) : (
    <form onSubmit={handleSubmit}>
      <label>Name</label>
      <input id="name" />
      <button type="submit" onClick={handleClick}>
        get a greeting
      </button>
    </form>
  )
}

Unfortunately, doing things that way means that you have to maintain TWO ways to
handle errors:

  1. Runtime errors
  2. fetchGreeting errors

Luckily, react-error-boundary also
exposes a simple hook to help with these situations as well. Here’s how you
could use that to side-step this entirely:

function Greeting() {
  const [{status, greeting}, setState] = React.useState({
    status: 'idle',
    greeting: null,
  })
  const handleError = useErrorHandler()

  function handleSubmit(event) {
    event.preventDefault()
    const name = event.target.elements.name.value
    setState({status: 'pending'})
    fetchGreeting(name).then(
      newGreeting => setState({greeting: newGreeting, status: 'resolved'}),
      error => handleError(error),
    )
  }

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

So when our fetchGreeting promise is rejected, the handleError function is
called with the error and react-error-boundary will make that propagate to the
nearest error boundary like usual.

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

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

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

  return status === 'resolved' ? (
    <div>{greeting}</div>
  ) : (
    <form onSubmit={handleSubmit}>
      <label>Name</label>
      <input id="name" />
      <button type="submit" onClick={handleClick}>
        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.

Note: you might be interested to know that the
implementation
of useErrorHandler is only 6 lines long 😉

Conclusion

Error Boundaries have been a feature in React for years and we’re still in this
awkward situation of handling runtime errors with Error Boundaries and handling
other error states within our components when we would be much better off
reusing our Error Boundary components for both. If you haven’t already given
react-error-boundary a try, definitely
give it a solid look!

Good luck.

Oh, one other thing. Right now, you may notice that you’ll experience that error
overlay even if the error was handled by your Error Boundary. This will only
happen during development (if you’re using a dev server that supports it, like
react-scripts, gatsby, or codesandbox). It won’t show up in production. Yes, I
agree this is annoying.
PRs welcome.

Errors are an inevitable part of development. They always seem to pop up at the most inopportune times to make sure our apps don’t work as intended. Regardless of how they find their way into our code, error handling is the antidote to these unintentional dev snafus, better known as exceptions.

In regular JavaScript, the try…catch statement offers an API with which we can try and gracefully catch potentially error-prone code:

try {
  console.log(p)
} catch (error) {
  console.log(`Error: ${error}`) // ReferenceError: p is not defined 
}

Any error detected in the try block is thrown as an exception and caught in the catch block, keeping our applications more resilient to errors. In React, this construct works fine for handling JavaScript errors as below, wherein we’re fetching data with useEffect:

useEffect(() => {
  try {
    fetchUsers();
  } catch(error) {
    setError(error);
  }
}, []);

But this doesn’t work so well in React components.

Why try...catch doesn’t catch JavaScript errors in React components

It’s wrong to think that by virtue of what it does and how it works that the try...catch statement can be used to catch JavaScript errors in React components. This is because the try...catch statement only works for imperative code, as opposed to the declarative code we have in components.

function ErrorFallbackUI({ errorMessage }) {
  return (
    <div className="article-error">
      <h3>There was a problem displaying the article:</h3>
      <h3 className="error">{errorMessage}</h3>
    </div>
  );
}

// There's an attempt to uppercase each publication title
function Publication({ publication }) {
  return (
    <a href={publication.href} className="article-cta">
      <h3 className="title">{publication.title.toUpperCase()}</h3>
      <h3 className="info">{publication.lead}</h3>
    </a>
  );
}

// Map over the list of publications and try to render a <Publication/>
function Publications({ publications }) {
  try {
    return publications.map((publication, index) => (
      <div key={index}>
        <Publication {...{ publication }} />
      </div>
    ));
  } catch (error) {
    return <ErrorFallbackUI {...{ errorMessage: error.errorMessage }} />;
  }
}

Try Catch JavaScript Error Fail Message

This error fallback UI has been clipped to show the relevant part. Dismiss the error UI to view the normal state of the application.

It’s important to note that the errors above will only show up in development. In production, the UI will be corrupted, and the user will be served a blank screen. How can we solve for this? Enter error boundaries.

N.B., tools like create-react-app and Next.js have an error overlay component that blocks the UI whenever there is an error, overshadowing your own error boundaries. In create-react-app, which this tutorial uses, we need to dismiss or close the overlay with the X mark in the upper right-hand corner to view our own error boundaries in use.

Using error boundaries in React

Error boundaries are React components that offer a way to gracefully handle JavaScript errors in React components. With them, we can catch JavaScript runtime errors in our components, act on those errors, and display a fallback UI.

import ErrorBoundary from "error-boundary";

function Users() {
  return (
    <div>
      <ErrorBoundary>
        {/* the rest of your application */}
      </ErrorBoundary/>
    </div>
  )
}

Error boundaries operate like the catch block in the JavaScript try...catch statement with a couple exceptions: they are declarative and, perhaps needless to say, only catch errors in React components.

More specifically, they catch errors in their child component(s), during the rendering phase, and in lifecycle methods. Errors thrown in any of the child components of an error boundary will be delegated to the closest error boundary in the component tree.

In contrast, error boundaries do not catch errors for, or that occur in:

  • Event handlers
  • Asynchronous code
  • Server-side rendering
  • Errors thrown in the error boundary itself

There are a few rules to follow for a component to act as an error boundary:

  1. It must be a class component
  2. It must define one or both of the lifecycle methods static getDerivedStateFromError() or componentDidCatch()

The rule of thumb is that static getDerivedStateFromError() should be used to render a fallback UI after an error has been thrown, while componentDidCatch() should be used to log those error(s).

The <PublicationErrorBoundary/> component below is an error boundary since it satisfies the necessary criteria:

class PublicationErrorBoundary extends Component {
  state = { error: false, errorMessage: '' };

  static getDerivedStateFromError(error) {
    // Update state to render the fallback UI
    return { error: true, errorMessage: error.toString() };
  }

  componentDidCatch(error, errorInfo) {
    // Log error to an error reporting service like Sentry
    console.log({ error, errorInfo });
  }

  render() {
    const { error, errorMessage } = this.state;
    const { children } = this.props;

    return error ? <ErrorFallbackUI {...{ error, errorMessage }} /> : children;
  }
}

The <PublicationErrorBoundary/> replaces the try...catch block from the <Publications/> component above with the following update:

function Publications({ publications }) {
  return publications.map((publication, index) => (
    <div key={index}>
      <PublicationErrorBoundary>
        <Publication {...{ publication }} />
      </PublicationErrorBoundary>
    </div>
  ));
}

Now, the error thrown from <Publication/> trying to uppercase each publication title allows for graceful exception handling. The error has been isolated and a fallback UI displayed.

Publication Typeerror Graceful Exception Handling Fallback UI

App UI with error boundary and a fallback UI.

You can see this example on CodeSandbox.

Where to place error boundaries in the component tree

The Next.js documentation does a good job of explaining the general rule of thumb: error boundaries shouldn’t be too granular; they are used by React in production and should always be designed intentionally.

Error boundaries are special React components and should be used to catch errors only where appropriate. Different error boundaries can be used in different parts of an application to handle contextual errors, though they can be generic — for example, a network connection error boundary.

Notice that in the prior example, the <PublicationErrorBoundary/> is used in Publications instead of Publication. If used otherwise, the error thrown from the attempt to uppercase each publication title won’t be caught by the <PublicationErrorBoundary/> because the children of <PublicationErrorBoundary/> will be a hyperlink and not a component. However, using it in <Publications/> solves for this.

// This won't work because the children of `PublicationErrorBoundary`
// aren't components
function Publication({ publication }) {
  return (
    <PublicationErrorBoundary>
      <a href={publication.href} className="article-cta">
        <h3 className="title">{publication.title.toUpperCase()}</h3>
        <h3 className="info">{publication.lead}</h3>
      </a>
    </PublicationErrorBoundary>
  );
}

Also, notice that each publication has an error boundary listening for it to err. What if it makes sense to display no publications at all if there’s an error with at least one of them? <Publications/> can be updated:

// Before
function Publications({ publications }) {
  return publications.map((publication, index) => (
    <div key={index}>
      <PublicationErrorBoundary>
        <Publication {...{ publication }} />
      </PublicationErrorBoundary>
    </div>
  ));
}

// After. Notice the location of `PublicationErrorBoundary`
function Publications({ publications }) {
  return (
    <PublicationErrorBoundary>
      {publications.map((publication, index) => (
        <div key={index}>
          <Publication {...{ publication }} />
        </div>
      ))}
    </PublicationErrorBoundary>
  );
}

The UI will be updated to show the fallback UI:

Error Boundary Fallback UI Updated Notice

Error boundary propagation

Similarly to how the catch block works, if an error boundary fails trying to render the error message, the error will propagate to the closest error boundary above it. If errors thrown are not caught by any of the error boundaries, the whole component tree will be unmounted.

This is a new behavior as of React 16, with the argument being that it is better to completely remove a corrupted UI than to leave it in place, and that it provides a better user experience in the event of an error.

<AppErrorBoundary>
  <App>
    <PublicationsErrorBoundary>
      <Publications>
        <PublicationErrorBoundary>
          <Publication />
        <PublicationErrorBoundary>
      </Publications>
    </PublicationsErrorBoundary>
  </App>
</AppErrorBoundary>

Take the contrived component tree above as an example. If an error is caught in <Publication/>, the closest error boundary, <PublicationErrorBoundary/>, will be responsible for it. In the event that it fails in its responsibility, <PublicationsErrorBoundary/> will act as a fallback. If that fails as well, <AppErrorBoundary/> will attempt to handle the error before the component is completely unmounted.

Resetting error boundaries

This is one of the edge cases I find in using error boundaries. Typically, this is done by resetting the UI state and recovering the UI from corruption.

Say a user loses connection, and a connection error boundary has succeeded in displaying a fallback UI alerting the user to check their connection. Can we recover the UI when the connection state becomes active? The simple answer is yes, and the other is that it depends.

Errors can be tricky, dynamic, and non-deterministic, and it is better you look into data fetching tools like SWR or React Query, or the react-error-boundary package, which alleviates these kinds of problems.

Conclusion

Wherever it is rational to do so, you should be using error boundaries to handle runtime errors in your React applications. The more familiar you get with them, the more you understand the reason for their existence.

With the react-error-boundary package specifically, you can reduce the amount of redundant code and uncertainty you encounter with a well-tested abstraction — which, as Kent Dodds puts it, is the last error boundary component anyone needs.

LogRocket: Full visibility into your production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time,
try LogRocket.

LogRocket Dashboard Free Trial Banner

LogRocket
combines session replay, product analytics, and error tracking – empowering software teams to create the ideal web and mobile product experience. What does that mean for you?

Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay problems as if they happened in your own browser to quickly understand what went wrong.

No more noisy alerting. Smart error tracking lets you triage and categorize issues, then learns from this. Get notified of impactful user issues, not false positives. Less alerts, way more useful signal.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps —
start monitoring for free.

While catching errors before they hit production is ideal, some of them, such as network errors, might slip through testing and impact your users.

If your React components are not properly catching errors thrown by third-party libraries or React hooks, such errors either end-up crashing the React lifecycle or reaching the top-level of the main execution thread, resulting in the “white screen” scenario:

As of React 16, errors that were not caught […] will result in unmounting of the whole React component tree

react errors bubbling-0rno1

It is crucial that your application gracefully handle such errors by providing proper visual feedback and potential actions (ex: retry mechanisms).

Fortunately, implementing such UX patterns can be achieved with little work with the React API and, for the most advanced UX, with the help of lightweight React libraries.

Using JavaScript’s try-catch around React hooks calls won’t work due to the asynchronous nature of their execution. However, React API offers the Error boundaries mechanism to catch all types of errors that might “bubble out” from a component.

For example, if the <ComponentA /> is wrapped in a React Error boundary, the error propagation will stop at the Error Boundary level, preventing the React App from crashing:

react errors bubbling with error boundary-cbe1m

This article will cover how to implement Error Boundaries in your application, from simple error catching to displaying visual feedback and providing retry mechanisms.

Simple Error Boundaries: Catching and Reporting Errors

Behind its sophisticated name, an Error Boundary is just a plain class React component implementing the componentDidCatch(error) method:

class ErrorBoundarySimple extends React.Component {

   componentDidCatch(error) {
      // report the error to your favorite Error Tracking tool (ex: Sentry, Bugsnag)
   }


   render() {
      return  this.props.children;
   }

}

Note: React is not yet offering a hook-based alternative to implement error boundaries.

As showcased in this CodeSandbox, the componentDidCatch() class method will be called as soon as an error reaches our MyErrorBoundary component, allowing us to prevent the React app from crashing and forwarding the error to our error reporting tool. (The CodeSandbox might display a development error overlay that only shows in development, you can dismiss it to see the rendering result).

Let’s make our <ErrorBoundarySimple> more friendly by adding simple visual feedback when errors are raised. For this, we add some state to ErrorBoundarySimple and use the getDerivedStateFromError() method, as follows: 

class ErrorBoundarySimple extends React.Component {
  state = { hasError: false };

  componentDidCatch(error: unknown) {
    // report the error to your favorite Error Tracking tool (ex: Sentry, Bugsnag)
    console.error(error);
  }

  static getDerivedStateFromError(error: unknown) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <p>Failed to fetch users.</p>;
    }

    return this.props.children;
  }
}

React expects the detDerivedStateFromError() method to return the state value to apply to out <ErrorBoundarySimple> when an error occurs.

As we can see on our live CodeSandbox, our UI is now providing visual feedback!

Error boundaries can also be nested to provide more contextualized feedback. For example, in this React app tree, we might want to provide different feedback based on what is crashing. For instance, we may want to provide different feedback when the Chat is crashing and when the TodoList is crashing, yet still handle any kind of crash at the application level. We can introduce multiple Boundaries to achieve this:

react errors nested boundaries-xjj4s

With the above setup, any error in the <Chat> component (or its descendant) would be caught in the Error Boundary wrapping the <Chat> component (not the “App” Error Boundary), allowing us to give a contextualized visual feedback. However, any error coming from all <App> descendants (excluding <Chat> and <TodoList>) will be caught by the “App” Error Boundary.

With a few lines of code, we just greatly improved our user experience by gracefully handling errors in our application.

However, such simple Error Boundaries implementations do have limitations. First, according to the React documentation, Error boundaries do not catch errors for:

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

And, the previously showcased Error Boundaries do not provide any action to the user to recover from the error, for example, with a retry mechanism. In the next section, we will see how to leverage the react-error-boundary library to handle all these edge cases.

Advanced Error Boundaries: Catching all Errors and Retry Mechanisms

Let’s now provide a superior error handling user experience by catching all kinds of errors and exposing recovery actions to the users. For this, we will use the react-error-boundary library which can be installed as follows:

npm install --save react-error-boundary

yarn add react-error-boundary

Provide a Retry Mechanism

Our new CodeSandbox defines a <Users> component that will fail to load users 50% of the time. (The CodeSandbox might display a development error overlay that only shows in development, you can dismiss it to see the rendering result).

Let’s use react-error-boundary to properly catch errors and provide a retry mechanism:

import { ErrorBoundary, FallbackProps } from "react-error-boundary";
import { Users } from "./Users";

function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) {
  return (
    <div role="alert">
      <p>Failed to load users:</p>
      <pre>{error.message}</pre>
      <button onClick={resetErrorBoundary}>Try again</button>
    </div>
  );
}

export default function App(): JSX.Element {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        {/* Users will fail to load 50% of the time */}
        <Users />
      </ErrorBoundary>
    </div>
  );
}

<ErrorBoundary> takes one mandatory FallbackComponent= prop that should be the react component or JSX that will be rendered in case of error. In the case of a component, this FallbackComponent= function will receive FallbackProps:

  • error can be used to display the error.
  • resetErrorBoundary is a callback to reset the error state and re-render the children’s components. 

An ononError prop can also be provided to forward the error to your favorite error reporting tool (ex: Sentry). The react-error-boundary documentation showcases how to leverage other props (ex: onReset=) to handle more advanced scenarios.

Catching all Errors

As aforementioned, Error boundaries do not catch errors for:

  • Event handlers (learn more)
  • Asynchronous code (e.g. setTimeout or requestAnimationFrame callbacks)

Because such errors happen outside of the React rendering lifecycle, Error boundaries won’t be invoked. Again, react-error-boundary has us covered by providing a handleError() hook that helps with catching event-related and asynchronous errors.

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),
      error => handleError(error),
    )
  }

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

Errors happening inside of handleSubmit() function won’t be caught by React rendering lifecycle. For this reason, we use the handleError function provided by react-error-boundary ’s useErrorHandler() to rethrow the error in the React lifecycle so that the nearest ErrorBoundary can catch it.

Conclusion

Behind its sophisticated name, a React Error Boundary is a straightforward way to gracefully handle any kind of error in a React application.

Good products should prevent errors from reaching production but also should use error boundaries to provide contextual feedback and recovery actions to their users in case of unexpected errors.

Meticulous

Meticulous is a tool for software engineers to catch visual regressions in web applications without writing or maintaining UI tests.

Inject the Meticulous snippet onto production or staging and dev environments. This snippet records user sessions by collecting clickstream and network data. When you post a pull request, Meticulous selects a subset of recorded sessions which are relevant and simulates these against the frontend of your application. Meticulous takes screenshots at key points and detects any visual differences. It posts those diffs in a comment for you to inspect in a few seconds. Meticulous automatically updates the baseline images after you merge your PR. This eliminates the setup and maintenance burden of UI testing.

Meticulous isolates the frontend code by mocking out all network calls, using the previously recorded network responses. This means Meticulous never causes side effects and you don’t need a staging environment.

Learn more here.

Authored by Charly Poly

Понравилась статья? Поделить с друзьями:
  • Use config tab is locked cisco как исправить
  • Usdownloader плагин вернул ошибку gdl error
  • Usd error что это значит
  • Urlopen error errno 2 name or service not known
  • Urlopen error errno 111 connection refused