Handling JavaScript Errors in React

Handling JavaScript Errors in React

Daryl Lukas's photo
Daryl Lukas
·Nov 4, 2022·

4 min read

Subscribe to our newsletter and never miss any upcoming articles

Play this article

Table of contents

  • What are React Error Boundaries?
  • How to Create an Error Boundary in React?
  • Why Use React Error Boundaries?
  • Final Words

An error boundary is an important design pattern that helps you deal with errors in your code. It helps you identify where and why an error has occurred and handle it gracefully. If you’re new to React or have never heard of it before, don’t worry.

Many developers have probably encountered an error boundary without knowing what it is called or how to use it. Let’s explore this design pattern and how we can use it in our code base.

What are React Error Boundaries?

Errors and exceptions happen in any application. They can be caused by incorrect input, external issues, and unexpected behavior. Before version 16, React was often criticized for its failure to handle JavaScript errors.

JavaScript errors inside components are used to corrupt React’s internal state and cause it to emit cryptic errors on next renders. React 16 introduced a new concept called error boundaries to solve this problem.

Error boundaries are React components that catch JavaScript errors anywhere in the component tree, log those errors, and display a fallback UI. They allow us to mark a section of our application as an error region. Inside the region, we can log the errors and return a different UI. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of the whole tree below them. However, they do not catch errors for:

  • Event handlers
  • Asynchronous code
  • Server-side rendering
  • Errors thrown in the error boundary itself (rather than its children)

How to Create an Error Boundary in React?

Let's start by creating a "buggy" component:

pages/BuggyCounter.jsx

import React from 'react';

class BuggyCounter extends React.Component {
  state = {
    counter: 0,
  };

  handleClick = () => {
    this.setState({
      counter: this.state.counter + 1,
    });
  };

  render() {
    if (this.state.counter === 5) {
      // Simulate an error!
      throw new Error('Simulated error.');
    }
    return (
      <div>
        <h1>{this.state.counter}</h1>
        <button onClick={this.handleClick}>+</button>
      </div>
    );
  }
}

export default BuggyCounter;

If you run this and click the + button until it reaches 5, an error (simulated in this case) will cause the component completely unmounts itself and the user is left with a blank page (this behavior may be different if running on a development server). This can be confusing to your users as they may not know what to do next. You will also notice an error message in the console.

An error message in the browser javascript console suggesting use of an error boundary

Let's fix this using an error boundary. Before we create one, here are a few things to note:

  • Only class components can be error boundaries.
  • A class component becomes an error boundary if it defines either (or both) of the lifecycle methods static getDerivedStateFromError() or componentDidCatch().
  • Use static getDerivedStateFromError() to render a fallback UI after an error has been thrown.
  • Use componentDidCatch() to log error information.

Now, let's create our error boundary.

components/ErrorBoundary.js

import React from 'react';

class ErrorBoundary extends React.Component {
  state = {
    error: null,
  };

  static getDerivedStateFromError(error) {
    // Update state so next render shows fallback UI.
    return { error: error };
  }

  componentDidCatch(error, info) {
    // Log the error to an error reporting service
    // Logging to console for demo purposes
    console.log(error, info);
  }

  render() {
    if (this.state.error) {
      // You can render any custom fallback UI
      return <p>Something broke</p>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

We then wrap our ErrorBoundary around BuggyCounter so that it catches the error.

App.jsx

import React from 'react';
import './App.css';
import ErrorBoundary from './components/ErrorBoundary';
import BuggyCounter from './pages/BuggyCounter';
function App() {
  return (
    <div className="App">
      <ErrorBoundary>
        <BuggyCounter />
      </ErrorBoundary>
    </div>
  );
}

export default App;

Our app will now gracefully handle the JavaScript error and display a friendly UI to the user, instead of completely crashing.

Image description

Why Use React Error Boundaries?

Errors are an inevitable part of building software. It is therefore considered good practice to create a component that is purpose-built as an Error Boundary instead of mixing error-handling logic into your generic components. However, Error Boundaries aren’t in direct competition with try/catch statements and should not substitute them. They’re both needed as a robust strategy for handling errors in React. Remember that Error Boundaries are only designed for intercepting errors that originate from 3 places in a React component

  • During render phase
  • In a lifecycle method
  • In the constructor

Final Words

Thanks for reading this article on how to deal with errors in React. I hope that this has helped you understand more about error boundaries and how they can help you in your projects. Check out the offical documentation for more on Error Boundaries.

Did you find this article valuable?

Support ReactPlay Blog by becoming a sponsor. Any amount is appreciated!

Learn more about Hashnode Sponsors