React component rendering even when there is no change in the state value

Viewed 58

From the React Docs, what I have learnt is that the component will re-render only if there is a change in the value of a state.

For instance

import React, { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  console.log("I am rendering");

  const handleButtonClick = () => {
    setCount(0);
  };
  return (
    <>
      <button onClick={handleButtonClick}>Increment</button>
      Count value is: {count}
    </>
  );
}

The message I am rendering is printed only once even if we click the button because the setCount function is setting the value to 0 which is the present value of count

Since there is no change in the present and future value therefore, the Component does not re-render.

Unexpected Behaviour

However, the similar behaviour is not observed when we add an extra line setCount(1) before setCount(0)

import React, { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  console.log("I am rendering");

  const handleButtonClick = () => {
    setCount(1); //this line has been added extra
    setCount(0);
  };
  return (
    <>
      <button onClick={handleButtonClick}>Increment</button>
      Count value is: {count}
    </>
  );
}

In principle, there is no change in the output of the final count value. However, if we click the button, the component re-renders and prints the message I am rendering


I could not find an explanation for this behaviour. Is this behaviour on expected lines?.

Shouldn't the component re-render only when the final value of the state is different from the current value ?

2 Answers

Sometimes, Reacts needs another render phase to decide if it needs a bailout. By the way, when we saying "bailout" meaning bailing out the Reconciliation process.

Notice the documentation on Bailing out a state update:

Note that React may still need to render that specific component again before bailing out.

Here is another example of such case demonstrating the idea:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";

const App = () => {
  const [state, setState] = React.useState(0);

  useEffect(() => {
    console.log("B");
  }, [state]);

  console.log("A");

  return (
    <>
      <h1>{state}</h1>
      <button onClick={() => setState(42)}>Click</button>
    </>
  );
};

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

Edit  React state trigger

You notice the next logs and their explanations:

A // First render
B // Mount

A // State change from 0 -> 42, triggers render
B // useEffect dep array change, triggers callback

A // **Our issue**, React needs another render

The value does change when you press the button. First, it changes to 1 then to 0 but this runs very fast that you don't get to see it.

to see this, you could add a setTimeout

const handleButtonClick = () => {
  setCount(1); //this line has been added extra
  setTimeout(() => {
    setCount(0);
  }, 500);
};
Related