User friendly errors with React error boundaries and fallback components
Tags: react, ux, errors, axios
Published: 2021-08-17 20:03:36

If we want to prevent our UI from crashing on errors and also have a fallback UI to show this errors in a friendly manner, we can use **React error boundary components** that wraps around critical parts of our app and catches JavaScript errors anywhere in it's child component tree.

***Complete code example with typescript [here](https://github.com/Leandrocoelho1/using-error-boundary).***


### Creating a custom error boundary component
Error boundaries are created as class components with access to two special lifecycle methods:
* `static getDerivedStateFromError()` which updates it's state to show the fallback UI.
* `componentDidCatch()` used to log error information.

```react
class ErrorBoundary extends React.Component {
  state: State = {error: null}

  static getDerivedStateFromError(error) {
    return {error}
  }

  componentDidCatch(error, errorInfo) {
    logErrorToMyService(error, errorInfo);
  }

  render() {
    const {error} = this.state
    if (error) {
      return <this.props.FallbackComponent error={error} />
    }
    return this.props.children
  }
}
```

In this example we are passing a `FallbackComponent` to be rendered *if our ErrorBoundary catches an error* and we are logging the error to a external service.

To use the ErrorBoundary component in our application we just need to wrap it around a component that might come across some errors. In this example I wrapped a component that fetches data from an API and passed a fallback component that shows an error message if something goes wrong:

```react
<ErrorBoundary
  // use key as a workaround for resetting the errorboundary state
  key={circuitName}
  FallbackComponent={CircuitErrorFallback}
>
  <CircuitContent />
</ErrorBoundary>
```
```react
function CircuitErrorFallback({error}) {
  return (
    <div role="alert">
      <h3>Something went wrong...</h3>
      <p>{error.message}</p>
    </div>
  )
}
```

The `<CircuitContent />` component will throw an error if something goes wrong with our API call:

```react
function CircuitContent({circuitName}) {
  const [state, setState] = useState<>({
    status: 'idle',
    circuit: {},
    error: null,
  })
  const {status, circuit, error} = state

  useEffect(() => {
    if (!circuitName) {
      return
    }
    setState(prevState => ({...prevState, status: 'pending'}))
    fetchCircuit(circuitName).then(
      circuit => {
        setState(prevState => ({...prevState, status: 'resolved', circuit}))
      },
      error => {
        setState(prevState => ({...prevState, status: 'rejected', error}))
      },
    )  
  }, [circuitName])

  if (status === 'idle') {
    return <CircuitIdle />
  } else if (status === 'pending') {
    return <CircuitLoading />
  } else if (status === 'rejected') {
    // throw error to be handled by error boundary
    throw error
  } else if (status === 'resolved') {
    return <CircuitDetails circuit={circuit} />
  }

  throw new Error('Something went really wrong.')
}
```

And ErrorBoundary will catch this error and render our fallback component:

![Error fallback component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hmkckza5pua3ghtny302.jpg)

### Using react-error-boundary

Creating our own error boundary component is pretty straight forward but we can also install `react-error-boundary` package on our app and use it's features for **resetting our error boundary and restoring the state of our UI**.

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

<ErrorBoundary
  onReset={handleReset}
  resetKeys={[circuitName]}
  FallbackComponent={CircuitErrorFallback}
>
  <CircuitContent circuitName={circuitName} />
</ErrorBoundary>
```

Now we can extend our fallback component with a button for reset the errorboundary:

```react
function CircuitErrorFallback({ error, resetErrorBoundary }) {
  return (
    <div role="alert">
      <h3>Something went wrong...</h3>
      <p>{error.message}</p>
      <button onClick={resetErrorBoundary}>
        Try again
      </button>
    </div>
  )
}
```

And the resulting error UI will look like this:

![Extended error fallback component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vv9rfy5yl4y661mqjp3c.jpg)

### Conclusion

We can wrap different parts of our applications with error boundaries to **keep our interface interactive** and prevent crashing. This can also benefit us during development stage while catching **errors that could even get unnoticed by typescript**.


#### Note on usage with Create React App:
CRA may display an overlay with error information in development mode **even if error boundary catches the error**. There are workarounds to change this behavior of Create React App but I think it's unecessary, since *you can press 'esc' to close the overlay and this will not be shown in production build anyway*.

#### Tip for handling error messages with Axios:
Axios will throw an error with a custom message like "The server responded with 404 status code." when an API call fails. You can use an axios interceptor to change this custom message to the actual error message in the API response body or even map it to something else:

```javascript
const api = axios.create({baseURL: 'https://api.backend.com'})
api.interceptors.response.use(
  response => response,
  error => {
    if (error.response.data.message) {
      error.message = error.response.data.message
    }
    return Promise.reject(error)
  },
)
```

*The idea for this post came from a lesson on the React hooks workshop from epicreact.dev. Thanks for reading!*