Error Handling in React Applications  by@edemagbenyo

Error Handling in React Applications

image
Edem Agbenyo HackerNoon profile picture

Edem Agbenyo

Passionate software developer with over 5 years of experience. Sedem's father.

When you are writing a react application, you have two ways to handle errors:

  • Using try-catch block in each component
  • Using React Error Boundary (only available in class Component :( )
import * as React from 'react'
import ReactDOM from 'react-dom'

function City({name}) {
  return <div>Hello, visit {name.toUpperCase()}</div>
}

function Country({capital}) {
  return <div>Hello, visit {capital.toUpperCase()}</div>
}

function App() {
  return (
    <div>
      <Country />
      <City />
    </div>
  )
}

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

The above piece of code would end up showing you an error page when you run it in Development or a blank screen in Production. Obviously, the error we created in the code above could have certainly been handled with PropTypes or TypeScript. However, we are aware runtime error happens all the time, and we are going to deal with them using the two approaches stated above.

Try/catch

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

function ErrorHandler({error}) {
  return (
    <div role="alert">
      <p>An error occurred:</p>
      <pre>{error.message}</pre>
    </div>
  )
}

function City({name}) {
  try {
    return <div>Hello, visit {name.toUpperCase()}</div>
  } catch (error) {
    return <ErrorHandler error={error} />
  }
}

function Country({capital}) {
  try {
    return <div>Hello, visit {capital.toUpperCase()}</div>
  } catch (error) {
    return <ErrorHandler error={error} />
  }
}

function App() {
  return (
    <div>
      <Country />
      <City />
    </div>
  )
}

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

This approach requires us to define an ErrorHandler component to display in case an error occurs, and we wrap each component returned element in the try/catch block.

This seems ok, but repetitive. What if we want the parent component to handle the error catching for us. Wrapping the parent component in a try/catch block will not work, due to the nature of how React calls functions. That is when React Error Boundary comes in.

According to React website, React Error Boundaries are defined as follow:

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those 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.

As at React 17.0.2, Error Boundary works only in

  • Class component
  • and It must implement static getDerivedStateFromError() or componentDidCatch()

In order to use Error Boundary in Functional Component, I use react-error-boundary.

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


function ErrorHandler({error}) {
  return (
    <div role="alert">
      <p>An error occurred:</p>
      <pre>{error.message}</pre>
    </div>
  )
}

function City({name}) {
    return <div>Hello, visit {name.toUpperCase()}</div>
}

function Country({capital}) {
    return <div>Hello, visit {capital.toUpperCase()}</div>
}

function App() {
  return (
    <ErrorBoundary FallbackComponent={ErrorHandler}>
      <Country />
      <City />
    </ErrorBoundary>
  )
}

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

When we run this application, we will get a nice error display from the content of component. React error boundary catches any error from the components below them in the tree. This is really handy and useful because we need not declare a separate try/catch for each component because the wrapping component(ErrorBoundary) takes care of that and display the component of the FallbackComponent provided.

Exceptions to error handling

Because react-error-boundary uses react error boundary in the background, there are a few exceptions to the errors that can be handled.

These errors are not handled by react-error-boundary

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

Error recovery

This library offers an error recovery feature that allows you to reset the state and bring back the components to a working point. Let's use this example from the react-error-boundary npmjs page.

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>
  )
}

The ErrorBoundary component accepts two other props to help recover from a state of error. The first prop onReset receives a function that will be triggered when resetErrorBoundary of the FallbackComponent is called. The onReset function is used to reset the state and perform any cleanup that will bring the component to a working state. The other prop of ErrorBoundary is resetKeys, it accepts an array of elements that will be checked when an error has been caught. In case any of these elements changes, the ErrorBoundary will reset the state and re-render the component.

Handling errors in React functional components should be a breeze for anyone using the react-error-boundary library. It provides the following features:

  • Fallback components to display in case of error
  • Granular capturing of error at the component level
  • Recovery of error using a function or by resetting the elements causing the component to fail.

Tags