Via the documentation you linked to in your question:
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. (React uses the Object.is
comparison algorithm.)
You can read there that React uses Object.is
when comparing previous and new state values. If the return value of the comparison is true
, then React does not use that setState
invocation in considering whether or not to re-render. That is why setting your state value to true
in the onClick
handler doesn't cause a rerender.
That said, unconditionally calling a setState
function at the top level of any component is always an error, because it initiates the reconciliation algorithm (infinitely). It seems to me that this is the core of your question, and if you want to learn about React Fiber (the implementation of React's core algorithm), then you can start here: https://github.com/acdlite/react-fiber-architecture
Here is another note from the React documentation (which needs to be updated for functional components):
You may call setState()
immediately in componentDidUpdate()
but note that it must be wrapped in a condition like in the example above, or you’ll cause an infinite loop.
↳ https://reactjs.org/docs/react-component.html#componentdidupdate
Explaining how class component lifecycle methods translate to functional components is out of scope for this question (you can find other questions and answers on Stack Overflow which address this); however, this directive applies to your case.
Here's a snippet showing that your component only renders once when the erroneous setState
call is removed:
<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone@7.16.3/babel.min.js"></script>
<div id="root"></div>
<script type="text/babel" data-type="module" data-presets="react">
const {useRef, useState} = React;
function Example () {
const renderCountRef = useRef(0);
renderCountRef.current += 1;
const [bool, setBool] = useState(true);
return (
<div>
<div>Render count: {renderCountRef.current}</div>
<button onClick={() => setBool(true)}>{String(bool)}</button>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
</script>