8

I’m currently following along with the React JS documentation and I’ve encountered an issue with the error boundaries not working as expected. I’ve tried replicating the example shown in the CodePen provided by the docs, and a few other simple examples I've found on the internet, however it is not working the same for me as it is in the demo and I’m struggling to understand why.

The exact issue is that the error is being thrown twice because the BuggyCounter component gets rendered an extra time. I do not understand why the component is rending a second time.

Please have a look at this minimal example.

import React, { Component } from 'react';

function App() {
  return (
    <ErrorHandler>
      <BuggyCounter />
    </ErrorHandler>
  );
}

class ErrorHandler extends Component {
  constructor(props) {
    super(props);
    this.state = {
      error: false,
      errorInfo: null
    }
  }

  componentDidCatch(error, errorInfo) {
    this.setState({ error, errorInfo });
  }

  render() {
    console.log('rendering ErrorHandler. ' + (this.state.error ? "error" : "no error"));
    if(this.state.error) {
      return <p>Error</p>
    }
    return this.props.children;
  }
}

class BuggyCounter extends Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
  }

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

  render() {
    console.log('rendering BuggyCounter. count: ' + this.state.counter);
    if (this.state.counter === 5) {
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>
  }
}

export default App;

The BuggyCounter component is being replaced with the <p> tag which renders “Error” (which is the desired effect), but only for a moment. Immediately after that, the default error page is being shown, defeating the purpose of Error Boundaries.

Here is my console:

my errors and debug messages

I would appreciate any information you could provide on this topic.

Temporary resolution:

It's not an answer to my question, but one way to prevent the redundant render is to throw the error from componentDidUpdate instead of render.

  render() {
    console.log('rendering BuggyCounter. count: ' + this.state.counter);
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>
  }

  componentDidUpdate() {
    if(this.state.counter === 5)
      throw new Error('I crashed');
  }
  • Good question, I have no Idea why, but maybe when a component throws an error, it rerender it's self? No clue. – Vencovsky Feb 03 '20 at 18:49
  • 2
    I could not replicate the error in a sandbox https://codesandbox.io/s/react-example-slnhn – Prasanna Feb 03 '20 at 18:51
  • 1
    A weird point to look at here is that if you add a log before throwing the error and in the `componentDidCatch` you will see that it throws the error twice, but it only passes in `componenDidCatch` once. – Vencovsky Feb 03 '20 at 18:51
  • @Prasanna same here – Vencovsky Feb 03 '20 at 18:53
  • 1
    If someone want to see the response from react https://github.com/facebook/react/issues/17966 – Vencovsky Feb 03 '20 at 19:32
  • I think since you didn't provide a fallback UI when the error is caught, React tries to recreate the component that crashed because you have an error boundary wrapping it. And that is the essence of error boundaries. – landrykapela Feb 03 '20 at 20:05
  • @landrykapela I thought that `return

    Error

    ` (on line 5 of BuggyCounter.render) would be the fallback UI. Is this not correct?
    – Anthony Yershov Feb 03 '20 at 20:10
  • Oh I see. Then you should use the static getDerivedStateFromError to change state which should trigger the rendering of the fallback UI. Check this resource https://reactjs.org/docs/error-boundaries.html? – landrykapela Feb 03 '20 at 20:19
  • @landrykapela I've modified my code to use `getDerivedStateFromError` instead of `componentDidCatch`. Unfortunately, that didn't affect the issue which led me to post this question. – Anthony Yershov Feb 03 '20 at 21:19

1 Answers1

3

Edit 2:

Well, the problem is in react version 16.12.0, if you change it to 16.0.0, it won't rerender twice. You can test this in this codesandbox by changing the react version.

This is a good issue to add at react github.

Probably is something internally in react core code. So depending of your version, it will render twice or only once.


Edit:

Why the component rerenders? No Idea.

But, the error page is only shown in development mode, so your componentDidCatch is working.


Old / Bad Answer

The BuggyCounter component is being replaced with the <p> tag which renders “Error” (which is the desired effect), but only for a moment. Immediately after that, the default error page is being shown, defeating the purpose of Error Boundaries.

The only for a moment part isn't true. The error page is acctually only for development, if you run it in production mode, it won't be shown.

And as you can see in my example, if you close the error page, you will see the error component.

This is explained in this answer.

So in the demo version provided by react docs, it doesn't show the error page because of it's configuration, not the code it self. Your code is working fine, just close the error page and see the results.

Vencovsky
  • 28,550
  • 17
  • 109
  • 176
  • The `BuggyCounter` must be getting rendered twice when `state.counter === 5` because the log shows "Rendering BuggyCounter. count: 5" two times. I believe you've explained why `ErrorHandler` is being rendered an extra time. – Anthony Yershov Feb 03 '20 at 18:18
  • I'm still unclear as to why `BuggyCounter` is performing a additional render after throwing an error. – Anthony Yershov Feb 03 '20 at 18:26
  • 1
    @AnthonyYershov that is because you are changing your state in `componentDidCatch`. State changes will rerender your component. – Prasanna Feb 03 '20 at 18:39
  • @Prasanna yes exactly. That is why `ErrorHandler` is getting rendered again. That is not my question. If you look at the log, `BuggyCounter` is being rendered an extra time before `ErrorHandler` is getting re-rendered due to state change. – Anthony Yershov Feb 03 '20 at 18:42
  • 1
    I can confirm that this issue does not exist in React 16.0.0 thank you for this information, and for raising this issue on the react github. – Anthony Yershov Feb 03 '20 at 19:37