Why does calling useState's setter with the same value subsequently trigger a component update even if the old state equals the new state?

The main question is, why logging in function component body causes 3 logs of "Component updated"?

The answer is hiding somewhere in React docs:

if you update a State Hook to the same value as the current state, React will bail out without rendering the children or firing effects.

Nothing new, but then:

Note that React may still need to render that specific component again before bailing out. That shouldn’t be a concern because React won’t unnecessarily go “deeper” into the tree.

But notice useEffect API definition:

will run after the render is committed to the screen.

If you log the change in useEffect you notice only two "B" logs as expected, which is exactly the example for bail out behavior mentioned:

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

  useEffect(() => {
    console.log("B");
  });

  console.log("A");

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

There will be an additional "Bail out" call for App component (extra "A" log), but React won't go "deeper" and won't change the existing JSX or state (no additional "B" will be logged).

Edit Q-65037566-Checking Render