This is the content of Act16, not the latest technology, but it is rarely discussed until the documentation turns out to be a useful part of it

Uncaught JS errors in React cause the entire application to crash and the entire component tree to be uninstalled. This has been the case since Act16. But React also introduced a new concept — error boundaries.

Definition, what is it

An error boundary is still a component that can catch (print or otherwise) JavaScript errors that process anywhere in the component’s subcomponent tree and render the alternate UI as needed.

Works like a try-catch, but the error boundary is only used for React components.

Only class components can be error bound components. Error boundaries can only catch errors of child components, not errors of their own.

Error boundaries catch errors during rendering, in the lifecycle, and in constructors throughout the component tree. Without error bounding, we would still render a broken subcomponent tree, which is obviously not what we want.

A step-by-step example of how to use error boundaries:

export default class ErrorTest extends Component { constructor(props) { super(props); } render() { return ( <div> <BugCounter></BugCounter> <span>my name is dan</span> </div> ); Class BugCounter extends Component {constructor(props) {super(props); this.state = { counter: 0, }; } click = () => { this.setState(({ counter }) => ({ counter: counter + 1 })); }; render() { if (this.state.counter === 5) { throw new Error("crashed!" ); } return ( <div> <h3 onClick={this.click}>{this.state.counter}</h3> </div> ); }}Copy the code

Render result of the code above (ignoring styles) :

If you click on the number 0, it increments. But when the number is equal to 5, the component raises an Error:

my name is Dan

In production mode, the screen goes blank and an error is displayed on the console:

getDerivedStateFromError & componentDidCatch

An error boundary is required to handle such crashes. How do you define an error boundary?

Define a component and implement the static getDerivedStateFromError() or componentDidCatch() lifecycle method (you can implement both or choose one). The component becomes an error boundary.

The two lifecycle functions can be viewed through the links, which are summarized as follows:

componentDidCatch(error, info)
Copy the code

Error is the error object thrown, and INFO contains information about the stack of errors thrown by the component. The function is called during the commit phase. It’s possible to perform side effects.

static getDerivedStateFromError(error)
Copy the code

Called after a child component throws an error, taking the thrown error as an argument. A value needs to be returned to update state. This function is called during the render phase and no side effects are allowed. If you need to perform a side effect operation after catching an error, you should do it in componentDidCatch.

Make the error boundary component

You can use composition to wrap a layer with an error bound component on top of the component you want to use. This component needs these effects:

  • Catch child component errors and record the error status internally
  • The standby UI is displayed in the error state and the child components are displayed in the normal state

Then it could go something like this:

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so that the next rendering can display the degraded UI
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also report error logs to the server
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can customize the degraded UI and render it
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; }}Copy the code

The side effects of catching an error are custom, uploaded to the server, or displayed on the page with a state record:

componentDidCatch(error, errorInfo) {
  // Catch errors in any components below and re-render with error message
  this.setState({
    error: error,
    errorInfo: errorInfo
  })
}
Copy the code

Capture process

Add all the code, wrap the problematic component with the component with the error boundary, and look at the result:

import { Component } from "react";

export default class ErrorTest extends Component {
  render() {
    return (
      <div>
        <ErrorBoundary>
          <BugCounter></BugCounter>
        </ErrorBoundary>
        <span>my name is dan</span>
      </div>); }}// Bug-reporting components
class BugCounter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      counter: 0}; } click =() = > {
    this.setState(({ counter }) = > ({ counter: counter + 1 }));
  };
  render() {
    if (this.state.counter === 5) {
      throw new Error("crashed!");
    }
    return (
      <div>
        <h3 onClick={this.click}>{this.state.counter}</h3>
      </div>); }}// Error bounding component
class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so that the next rendering can display the degraded UI
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // You can customize the degraded UI and render it
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; }}Copy the code

Throwing an exception is still an error in development mode, but after using YARN Build and hanging through http-server, access the production page:

As you can see, the display of my Name is Dan is not affected by the throw Error console error, that is, the child component error inside the error boundary does not affect other components and elements outside.

scope

Error bounds are used to handle errors on child component life cycles and rendering functions. For event handlers, they are not raised during rendering, and exceptions thrown by event handlers should be tried and caught.

Error bounds cannot catch errors in these scenarios:

  • The event processing
  • Asynchronous code
  • Server side rendering
  • Errors thrown by the error boundary itself (non-child components)

There’s another official React demo worth trying out for error boundaries:

Codepen. IO/gaearon/pen…


Reference:

  • Zh-hans.reactjs.org/docs/error-…

  • Zh-hans.reactjs.org/docs/react-…

  • Codepen. IO/gaearon/pen…