question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

React re-executes render() right after an exception has been thrown in the same render() method

See original GitHub issue

Do you want to request a feature or report a bug? I would like to clarify a doubt regarding this behaviour (maybe a bug? I don’t know).

What is the current behavior? By playing with the Error Boundaries demo:

https://codepen.io/gaearon/pen/wqvxGa?editors=0010

I discovered that when an exception is thrown in render(), React re-executes that same render() method before running the corresponding Error Boundary’s code.

Check out the same code with a console.log line added both to BuggyCounter.render() and componentDidCatch():

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { error: null, errorInfo: null };
  }
  
  componentDidCatch(error, errorInfo) {
    // Catch errors in any components below and re-render with error message
    console.log('componentDidCatch') // <------------------- LOG
    this.setState({
      error: error,
      errorInfo: errorInfo
    })
    // You can also log error messages to an error reporting service here
  }
  
  render() {
    if (this.state.errorInfo) {
      // Error path
      return (
        <div>
          <h2>Something went wrong.</h2>
          <details style={{ whiteSpace: 'pre-wrap' }}>
            {this.state.error && this.state.error.toString()}
            <br />
            {this.state.errorInfo.componentStack}
          </details>
        </div>
      );
    }
    // Normally, just render children
    return this.props.children;
  }  
}

class BuggyCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { counter: 0 };
    this.handleClick = this.handleClick.bind(this);
  }
  
  handleClick() {
    this.setState(({counter}) => ({
      counter: counter + 1
    }));
  }
  
  render() {
    console.log('this.state.counter', this.state.counter) // <------------------- LOG
    if (this.state.counter === 5) {
      // Simulate a JS error
      throw new Error('I crashed!');
    }
    return <h1 onClick={this.handleClick}>{this.state.counter}</h1>;
  }
}

function App() {
  return (
    <div>
      <p>
        <b>
          This is an example of error boundaries in React 16.
          <br /><br />
          Click on the numbers to increase the counters.
          <br />
          The counter is programmed to throw when it reaches 5. This simulates a JavaScript error in a component.
        </b>
      </p>
      <hr />
      <ErrorBoundary>
        <p>These two counters are inside the same error boundary. If one crashes, the error boundary will replace both of them.</p>
        <BuggyCounter />
        <BuggyCounter />
      </ErrorBoundary>
      <hr />
      <p>These two counters are each inside of their own error boundary. So if one crashes, the other is not affected.</p>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
      <ErrorBoundary><BuggyCounter /></ErrorBoundary>
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

After the first rendering I see (OK):

Screen Shot 2019-10-21 at 23 33 31

After clicking on the counter 4 times (OK):

Screen Shot 2019-10-21 at 23 33 43

Then, after clicking once more (Why does render() gets executed twice?):

Screen Shot 2019-10-21 at 23 33 54

After that, componentDidCatch() is, as expected, executed only once.

What is the expected behavior? Cannot tell. This is not a problem in se because of the idempotent nature of render() (which has to be pure), but makes me think that this is an unneeded call because if an error has been thrown in render(), re-executing it right away would still lead to the same exception being thrown.

Or, is there something I am missing? Maybe related to how React works internally and because of that it needs to recall render() as soon as that same render() has thrown…

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

OS: OS X Browser: Chrome 77 Using the same React 16 development version of the Pen:

https://unpkg.com/react@16/umd/react.development.js https://unpkg.com/react-dom@16/umd/react-dom.development.js

Thank you for the attention.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:7 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
gaearoncommented, Apr 1, 2020

Yes, React will call it twice. There are some obscure reasons why we do this. As you said, due to idempotency of render it shouldn’t matter.

0reactions
tonix-tuftcommented, Apr 5, 2020

Tried to do that, it happens within the invokeGuardedCallback function:

  /**
   * Call a function while guarding against errors that happens within it.
   * Returns an error if it throws, otherwise null.
   *
   * In production, this is implemented using a try-catch. The reason we don't
   * use a try-catch directly is so that we can swap out a different
   * implementation in DEV mode.
   *
   * @param {String} name of the guard to use for logging or debugging
   * @param {Function} func The function to invoke
   * @param {*} context The context to use when calling the function
   * @param {...*} args Arguments for function
   */

  function invokeGuardedCallback(name, func, context, a, b, c, d, e, f) {
    hasError = false;
    caughtError = null;
    invokeGuardedCallbackImpl$1.apply(reporter, arguments);
  }

Still didn’t understand why, though.

Anyway, thanks for replying. 😃

Read more comments on GitHub >

github_iconTop Results From Across the Web

Error Boundaries - React
Use static getDerivedStateFromError() to render a fallback UI after an error has been thrown. Use componentDidCatch() to log error information.
Read more >
8 common React error messages and how to address them
React Hooks must be called in the exact same order in every component render; React Hook has a missing dependency: 'XXX'.
Read more >
Documentation - SolidJS · Reactive Javascript Library
import { onMount } from "solid-js"; function onMount(fn: () => void): void;. Registers a method that runs after initial render and elements have...
Read more >
How To Handle Async Data Loading, Lazy Loading, and Code ...
React has a built-in system for lazy loading components, ... The function will run on the first render after the layout and paint....
Read more >
Can you force a React component to rerender without calling ...
render() , it works fine. So I guess my question is: do React components need to have state in order to rerender? Is...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found