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

ⅰ亾dé卋堺 提交于 2021-02-07 13:22:32

问题


This problem occurs only if the state value was actually changed due to the previous update.

In the following example, when the button is clicked for the first time, "setState" is called with a new value (of 12), and a component update occurs, which is understandable.

When I click the same button for the second time, setting the state to the same value of 12 it causes the component to re-run (re-render), and why exactly that happens is my main question.

Any subsequent setStates to the same value of 12 will not trigger a component update, which is again, understandable. 12 === 12 so no update is needed.

So, why is the update happening on the second click of the button?

export default function App() {
  const [state, setState] = useState(0);

  console.log("Component updated");

  return (
    <div className="App">
      <h1>Hello CodeSandbox {state}</h1>
      <button onClick={() => setState(12)}>Button</button>
    </div>
  );
}

Codesandbox example


回答1:


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).




回答2:


Adding to the generally correct accepted answer, there is what i've found diving deeper in that problem:

There is actually more complex mechanics in that.

Actually, any setState call is applying reducer function under the hood, and that reducer function runs on next useState call, not before component function execution.

So normally there is no way of knowing if new state will be the same or not without executing executing that reducer (on useState call).

On the other hand however, when such reducer was once executed and state was not changed, component's return is ignored (render skipped) and next call of that reducer will be executed before component's function and NOT when useState called. That is also true for the very first setState of the component's life.

I've made a demo of that in codesandbox



来源:https://stackoverflow.com/questions/65037566/why-does-calling-usestates-setter-with-the-same-value-subsequently-trigger-a-co

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!