Neville Kati /

~ min read

Handling Errors in React JS

When working with applications, encountering errors and bugs is common, even in production environments. Regardless of the issue, it’s crucial to communicate these errors clearly and in a friendly manner, within a well-designed user interface.

This is a crucial aspect of developing user-friendly React applications. While it may be nearly impossible to predict or completely prevent errors, you can definitely control how they are handled.

There are various types of errors in React, for example:

  • Synax Errors: These occur when there are mistakes in the structure of your code, such as missing semicolons, typos, mismatched parentheses, or incorrect use of brackets.

  • Reference Errors: These occur when you try using variables or functions that have not been defined.

  • Type Errors: These occur when an operation is performed on a value of an incorrect data type.

Once you or your users encounter these errors in production, you are going to get the white screen of sadness 😕:

White screen of sadness

This is ofcourse terrible user experience, as we want to clearly communicate to our users what is happening in the application in the event of an error.

Error Boundaries

An "Error Boundary" is a special component that handles runtime errors and lets you display some fallback UI. For a component to function as an Error Boundary:

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

A basic implementation for an Error Boundary, would look something like this:

class ErrorBoundary extends React.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 }
  }

  componentDidCatch(error, info) {
    // add any extra logic, like logging the error to an error reporting service.
    logErrorToSomeService(error, info)
  }

  render() {
    if (this.state.hasError) {
      // pass a prop value for fallbackUI. This will be displayed whenever there is an error.
      return this.props.fallbackUI
    }
    return this.props.children
  }
}

Then you can wrap a part of your component tree with it:

<ErrorBoundary fallbackUI={<div>Something went wrong</div>}>
  <UserDetails />
</ErrorBoundary>

If UserDetails or any of its child components throw an error, ErrorBoundary will "catch" that error, display the fallback UI passed via the "fallbackUI" prop and send an error report to the reporting service provided.

Fortunately, we don't need to handle all of this manually because the React Error Boundary library provides all the tools we need to declaratively manage runtime errors.

Not all errors are caught 🤦🏾‍♂️

Unfortunately, there are some errors that React cannot or does not pass to Error Boundaries.

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

The "react-error-boundary" package provides a useErrorBoundary hook that returns a showBoundary function. This function can be called to trigger the Error Boundary, making it especially useful for handling errors in scenarios where the Error Boundary is not directly invoked. For example:

function UserDetails() {
  const [user, setUser] = React.useState({
    address: '',
    age: '',
  })
  const { showBoundary } = useErrorBoundary()

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

    fetchUser(name).then(
      (data) => setUser({ address: data.address, age: data.age }),
      (error) => showBoundary(error),
    )
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>Name</label>
      <input id="name" />
      <button type="submit" onClick={handleClick}>
        get user details
      </button>
    </form>
  )
}

So when our fetchUser promise is rejected, the showBoundary function is called with the error and react-error-boundary will propagate that error to the closest error boundary.

Where to place Error Boundaries

You don't need to wrap every component into a separate error boundary, however:

  • You should consider where it makes sense to display an error message.

  • It’s advisable to wrap your entire application in an Error Boundary. This way, any errors that occur will be caught and handled in a more user-friendly manner, regardless of where they happen in the application.

  • Components that depend on data coming from an external source, should be wrapped in an Error Boundary.

Conclusion

Effective error handling is essential for providing users with a valuable experience in your application. By understanding the different types of errors and implementing appropriate strategies, you can enhance the reliability and overall user experience of your React applications.

The react-error-boundary package is an amazing tool to help us manage our errors more effectively. Thanks for reading! Good luck!