简体   繁体   中英

React - weird behavior when useState hook sets state for the first time vs subsequent times

consider this code:

 const {useState} = React; function App() { const [count, setCount] = useState(0); const onClick = () => { setCount((prevCount) => { console.log(prevCount + 1); return prevCount + 1; }); setCount((prevCount) => { console.log(prevCount + 1); return prevCount + 1; }); setCount((prevCount) => { console.log(prevCount + 1); return prevCount + 1; }); console.log("onclick"); }; console.log("rendering"); return <button onClick={onClick}> Increment {count} </button>; } ReactDOM.render(<App/>, document.getElementById('app'))
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script> <div id="app"></div>

When run and the button is clicked, this is the output it produces:

1
onclick 
2
3
rendering 

I would have expected the output to be

onclick 
1
2
3
rendering

as I am using the updater function to access previous stat and state updates are batched and async by default.

And expectedly so, further click on the button confirm this, and produce this output:

onclick 
4
5
6
rendering

I suspect that the first set state is always synchronous in case of hooks, because if I change my code to this:

function App() {
  const [count, setCount] = useState(0);

  const onClick = () => {
    // add this extra set state before any other state updates
    setCount(1);
    setCount((prevCount) => {
      console.log(prevCount + 1);
      return prevCount + 1;
    });

    setCount((prevCount) => {
      console.log(prevCount + 1);
      return prevCount + 1;
    });

    setCount((prevCount) => {
      console.log(prevCount + 1);
      return prevCount + 1;
    });

    console.log("onclick");
  };

  console.log("rendering");

  return <button onClick={onClick}> Increment {count} </button>;
}

the output as expected is:

onclick 
2
3
4
rendering 

I haven't been able to find any explanations for this online yet. This would not impact any functionality as much as I can see, because although the first time it is executed synchronously it still updates the state asynchronously, which means I can't access the updated state in console.log("onclick" + count)

Would be helpful to get an explanation of why it works like this.

NOTE: discussed this on github. Seems like this is one of the things that as consumers we should not care about. It is an implementation detail. https://github.com/facebook/react/issues/19697

React will merge all state changes into one update to reduce rendering as rendering costs a lot, setState is not surely updated when you call log. So you can just log("count="+(count+3)) or use useEffect .

useEffect sets a callback when state/prop change; the following snippet shows how to log count every time it changes. You can reference the docs for more information.

 const {useState,useEffect} = React; function App() { const [count, setCount] = useState(0); useEffect(()=>{ console.log("effect!count=" + count) },[count]) const onClick = () => { setCount((prevCount) => { console.log(prevCount + 1); return prevCount + 1; }); setCount((prevCount) => { console.log(prevCount + 1); return prevCount + 1; }); setCount((prevCount) => { console.log(prevCount + 1); return prevCount + 1; }); console.log("onclick"); }; console.log("rendering"); return <button onClick={onClick}> Increment {count} </button>; } ReactDOM.render(<App/>, document.getElementById('app'))
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script> <div id="app"></div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM