Why does React call my component more times than needed?

Viewed 791

I have this simple example of a component that uses useRef() to count the number of times my component has rendered, and useState() to maintain some state:

const {useState, useRef} = React;
const App = () => {
  const [counter, setCounter] = useState(0);
  const renderCount = useRef(0);
  const onClick = () => {
    setCounter(1);
  }
  renderCount.current++;
  alert("rendering"); // executes 3 times, not twice.
  return <div>
    <p>Render count: {renderCount.current}</p>
    <button onClick={onClick}>{counter}</button>
  </div>
}

ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.0/umd/react-dom.production.min.js"></script>

When I run the snippet above, the alert triggers and the ref counter is incremented due to the first render. When I click the button for the first time, setCounter(1) is called, which cuases the component to rerender/reexecute (as expected) causing the alert to appear for a second time and the ref counter to increase. When I click the button for the second time, setCounter(1) runs (which isn't updating the state since it is already 1). As the state isn't changing, I would expect that there is no rerender needed and so my App component wouldn't be executed again. However, I instead see a third alert, indicating that the component was executed again, but the ref counter doesn't increase, nor does the rendered output change after the second click. Further clicks of the button show no alerts as expected. My question is why does React execute my App component again when the state doesn't change (ie: on the second click of the button)?

2 Answers

This is rendering behavior of React for re-rendering and this is a special case. If you update the state to same value as the current state, then React re-render the component one more time and bail out from subsequent re-renders.

  • The first render is the initial render and the state is 0
  • The second render is for the state change state is 1
  • The third render is for the special case ( this is not the initial render ) state is 1

However this unnecessary re-render is not the case for the initial render. I mean if you use setCounter(0) in the onClick method instead of setCounter(1), then the component will be rendered only once even if onClick method is called subsequently.

The flow is like in the following diagram:

enter image description here

The reason is; React can’t guess the output of rendering won't change, it has to render one more time and compare the results with the previous render. But I am not sure how it is handled in the initial state (maybe a flag). There is not much information about it in the docs.

You might think that there is an efficiency problem but really there is not. There are two phases to the React rendering cycle: The render phase and the commit phase. DOM is updated after the commit phase and in this situation the process will not go to the commit phase and will not lead DOM update

You are not using React's hook useEffect(), this is why your function, called anytime, calls it's local scoped functions. Think of your function component as of a letter. You pass it to the letterman closed and signed. But in your case, you passed it opened (during transfer it was read and called) but signed (it knows where it will be), hence you need to use hooks / lifecycle methods to control your React component.

That's why you are experiencing the alerts and incrementations.

If you have already worked with React class components, then you should know what's it lifecycle. If not, I encourage you to read this article: React Lifecycles.

useEffect has a behaviour of connection between componentDidMount, componentDidUpdate and componentWillUnmount. Anytime you update your state, or props, this method will be called and will execute given code.

Related