Hello, everyone. I am Karsong.

This article explains the full implementation logic of Error Boundaries in React.

Summary in a picture:

Here’s a quick guide to the React workflow. There are three steps:

  1. Triggered update

  2. Render phase: Calculates the side effects that updates will cause

  3. Commit phase: Side effects are performed in the host environment

There are many side effects, such as:

  • Insert DOM node

  • Execute the useEffect callback

All right, let’s get to the point.

Welcome to join the Human high quality front-end framework research group, Band fly

What are Error Boundaries

React provides two error-handling apis:

  • GetDerivedStateFromError: Static method that provides an opportunity to render the Fallback UI if an error occurs

  • ComponentDidCatch: Component instance method that provides an opportunity to log error information when an error has occurred

Classcomponents that use these two apis are often called Error Boundaries.

All errors in the React workflow that occur in the descendants of Error Boundaries component will be caught by Error Boundaries.

The React workflow refers to:

  • Render phase

  • The commit phase

Consider the following code:

class ErrorBoundary extends Component {
  componentDidCatch(e) {
    console.warn(" error ", e); }render() {
    return <div>{this.props.children}</div>; }}const App = () = > (
	<ErrorBoundary>
    <A><B/></A>
    <C/>
	<ErrorBoundary>
)
Copy the code

A, B and C, as descendants of ErrorBoundary, will be captured by componentDidCatch method in ErrorBoundary when errors occur in React workflow.

Step 1: Catch errors

Let’s start by looking at when errors are caught in the workflow.

The core code for the Render phase is as follows, and errors that occur are handled by handleError:

do {
  try {
    // For concurrent updates, workLoopConcurrent
workLoopSync();
    break;
  } catch(thrownValue) { handleError(root, thrownValue); }}while (true);
Copy the code

The COMMIT phase involves a lot of work, such as:

  • ComponentDidMount/Update

  • Bind/unbind ref

  • UseEffect/useLayoutEffect callback and destroy

This work is performed as follows, with errors being handled by captureCommitPhaseError:

try {
/ /... Perform a task
} catch (error) {
  captureCommitPhaseError(fiber, fiber.return, error);
}
Copy the code

Step 2: Construct the callback

You can see that even without Error Boundaries, the errors in the workflow have been caught by React. The correct logic would be:

  • If there are Error Boundaries, implement the API

  • The React prompt message is displayed

  • If no Error Boundaries exist, throw uncaught errors

So either handleError or captureCommitPhaseError starts at the parent of the node where the Error occurred and goes up layer by layer looking for the nearest Error Boundaries.

Once found, it constructs:

  • Callbacks for executing the Error Boundaries API

  • Callback for raising React prompts

  / /... The logic is abridged for readability
function createClassErrorUpdate() {
  if (typeof getDerivedStateFromError === 'function') {
Callback for getDerivedStateFromError
    update.payload = () = > {
      return getDerivedStateFromError(error);
};
// Callback for the React prompt
    update.callback = () = > {
      logCapturedError(fiber, errorInfo);
    };
  }
  if(inst ! = =null && typeof inst.componentDidCatch === 'function') {
Callback for executing componentDidCatch
    update.callback = function callback() {
      this.componentDidCatch(error);
    };
  }
  return update;
}
Copy the code

If Error Boundaries are not found, continue up to the root node.

Construct:

  • Callback used to throw uncaught errors

  • Callback for raising React prompts

/ /... The logic is abridged for readability
funffction createRootErrorUpdate() {
  // Callback for throwing "uncaught errors" and "React prompt messages"
  update.callback = () = > {
    onUncaughtError(error);
    logCapturedError(fiber, errorInfo);
  };
  return update;
}
Copy the code

Implement the callback

When will the constructed callback be executed?

React has two apis that perform user – defined callbacks:

  • forClassComponent.this.setState(newState, callback)In thenewStateandcallbackParameters can be passedFunctionAs acallback

So Error Boundaries automatically trigger an update:

this.setState(() = > {
  Callback for getDerivedStateFromError
}, () = > {
  Callback for executing componentDidCatch
  // And the callback used to raise the React prompt
})
Copy the code
  • For the root node, executeReactDOM.render(element, container, callback)In thecallbackParameter energy transferFunctionAs acallback

So, in the case of no Error Boundaries, the following function is actively executed:

ReactDOM.render(element, container, () = > {
// Callback for throwing "uncaught errors" and "React prompt messages"
})
Copy the code

So the implementation of Error Boundaries can be thought of as new functionality implemented by React using an existing API.

conclusion

The question is often asked: Why do Hooks have no Error Boundaries?

As you can see, the Error Boundaries implementation takes advantage of the fact that this.setState can pass callbacks, but useState is not fully benchmarked yet.

Finally, for your homework, the four errors described in the official document will not be caught by Error Boundaries.

Using this knowledge, can you analyze why they are not captured?