Home > database >  React: Under what conditions does setState from the useState hook cause a re-render?
React: Under what conditions does setState from the useState hook cause a re-render?

Time:11-17

I have the following code (CodeSandbox):

function App() {
  const [blah, setBlah] = useState(true);
  console.log('BLAH', blah);
  setBlah(true);

  return <button onClick={() => setBlah(true)}>Blah</button>;
}

I understand that setBlah(true) at the top level of the component is causing too many re-renders. What I don't understand is if you comment out the top level setBlah(true) and start mashing the "Blah" button, why does the component not re-render? Both are setting the state of blah to true over and over, yet only the top level setBlah(true) causes a re-render.

From the React docs on Bailing out of a state update: "Note that React may still need to render that specific component again before bailing out." The key word here is "may", which to me means that my situation is valid according to the docs. So the question might become, under what conditions will calling setState, with the same value that is already set, cause a re-render? It would be useful to know this as one might want to put logic in front of their setState method to check if the value is the same as the current so as to not cause an erroneous re-render.

CodePudding user response:

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 being said, idempotently 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/[email protected]/umd/react.development.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/[email protected]/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>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

CodePudding user response:

From my comment - Since you've already set the default of blah to true -

const [blah, setBlah] = useState(true);

Mashing the button is not actually changing the state since it's already true. However, if you do something like:

return <button onClick={() => setBlah(!blah)}>Blah</button>;

You'll see it switch between true and false in the console every time you hit the "Blah" button.

It's the

setBlah(true);

in the constructor that's causing the loop. It already set the state to true, then you're telling it to set it again in the same constructor.

  • Related