/  Technology   /  How To Use Error Boundaries in React?
How To Use Error Boundaries in React

How To Use Error Boundaries in React?

 

Error Boundaries were introduced in React v16 as a way to catch tricky errors that occur during the react render phase. In the past, this would have caused the web app to react unmount completely, and the user would just see a blank(white page) web page, which is not ideal!

Encountering Errors without Error Boundaries

We will inevitably encounter unexpected errors in our react apps during app development. You could be trying to access a deeply-nested property on an react object that does not exist, or sometimes it is not in our control (like a failed HTTP request to a third-party API).

In the demo below, we will simulate an each and every error to see what normally happens without an Error Boundary.

Counter.js

import React from 'react';
class Counter 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 Counter;

Output

Uncaught Error: Simulated error.

When the react app encounters an error, the react component completely unmounts itself and the user is left with a blank(white page ) HTML page. This can leave users feeling confused and they will not know what to do next step.

Error Boundaries providing a way to gracefully handle these errors!

Encountering Errors with Error Boundaries

What are Error Boundaries coming exactly? Contrary to what you may think, it is not a new react component or JavaScript(JS) library. It is more like a strategy for handling errors in React components.

Specifically, it is the mainly usage of two methods that are available in React components:

Example.js

import React from 'react';
class Example 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
    logErrorToExampleService(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 Example;
  • static getDerivedStateFromError is a react lifecycle method that allows the Error Boundary a chance to update the react state and thus triggering a last render(). In the above code snippet, the react state is providing to reveal a human-friendly error message instead of the broken react component (e.g., this.props.children).
  • componentDidCatch is a react lifecycle method designed for triggering side-effects (e.g., logging the error to tools like Crashlytics). You can access info.componentStack to get a developer-friendly stack trace that will be more useful for triaging the bug.

Any React Components is considered an Error Boundary when it employs at least one of these react lifecycle methods.

Good practices suggest that you will need to create a react component that is purpose-built as an Error Boundary instead of mixing error-handling logic into your generic react components.

Let’s slightly modify <ErrorBoundary>, and then wrap it around <BugComponent> so it will be catch the error!

import React from 'react';
class ErrorBoundary extends React.Component {
  state = {
    errorMessage: '',
  };
  static getDerivedStateFromError(error) {
    return { errorMessage: error.toString() };
  }
  componentDidCatch(error, info) {
    this.logErrorToServices(error.toString(), info.componentStack);
  }
  // A fake logging service.
  logErrorToServices = console.log;
  render() {
    if (this.state.errorMessage) {
      return <p>{this.state.errorMessage}</p>;
    }
    return this.props.children;
  }
}
export default ErrorBoundary;

App.jsx

import React from 'react';
import Counter from './Counter';
import ErrorBoudnary from './ErrorBoundary';
class App extends React.Component {
  refreshPage = () => {
    history.go(0);
  };
  render() {
    return (
      <div>
        <ErrorBoundary>
          <Counter />
        </ErrorBoundary>
        <hr />
        <button onClick={this.refreshPage}>Refresh Page</button>
      </div>
    );
  }
}
export default App;

Counter.js remains the same.

Try clicking the + (increment) button again. Once it reaches count 5, it will crash gracefully. Additionally, you can open your web page console to view a error stack trace!

Instead of completely crashing, you can use Error Boundaries to substitute a fallback UI page. This provides more visual feedback to the user that something broke while allowing them to continue interacting with your web app.

They can choose to navigate away or/and even reach out to customer service to help resolve their situation! It;s a great way to redeem and  otherwise unfortunate user experience.

Comparing Error Boundaries and Try…Catch

Error Boundaries normally not in direct competition with try…catch react statements. The error Boundaries are only designed for intercepting errors that originate from three places in a React components:

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

Basically… the React-y parts of a react component.

As a counter point, these are the places where react Error Boundaries will not be able to catch an error:

  • Event handlers (e.g., onClick, onChange, etc.).
  • setTimeout or requestAnimationFramecallbacks.
  • Server-side rendering (SSR).

And errors caused by the react error boundary itself (rather than its children).

So Error Boundaries do not really impact how you use try…catch. They are both want as a robust strategy for handling errors in React.

 

Leave a comment