简体   繁体   中英

React useState hook does not seem to update the state on explicitly calling setter function

This is the code written

function App() {

  const [number, setNumber] = useState(1);
  const mapper = {
    1: 'One',
    2: 'two',
    3: 'three'
  }
  const setFirst = () => {
    setNumber(1);
    console.log(number);
    console.log(mapper[number]);
  }
  const setSecond = () => {
    setNumber(2);
    console.log(number);
    console.log(mapper[number]);
  }
  const setThird = () => {
    setNumber(3);
    console.log(number);
    console.log(mapper[number]);
  }
  return (
    <div>
      <button onClick={() => { setFirst(); }}>One</button>

      <button onClick={() => { setSecond() }} >Two</button>

      <button onClick={() => { setThird(); }} >Three</button>
    </div>
  );
}

Expected: On click of setFirst() , number should be set to 1. On click of setSecond() , number should be set to 2. On click of setThird() , number should be set to 3.

What's happening is

On clicking in sequence setFirst() -> setSecond() -> setThird() in repeating fashion

Output:

1
One
1
One
2
Two
3
Three
1
One

Expected output:

1
One
2
Two
3
Three
1
One
2
Two

Can someone help me with this. I need help in figuring out where the bug is.

As Chris said in comment, setNumber is an asynchronous function, so its update is not visible right after it is performed.

Moreover, you should know that, at each render, each methods inside the component is "stacked" inside its current closure. Let me elaborate:

  1. You render the component, which returns the button (assume just the first one). When the button is rendered, since setFirst it's linked to it, you can imagine a sort of room being created, in which all the external variables used inside setFirst are copied. Thus, since setFirst uses the variables number and mapper , a copy of them is created inside this "room";
  2. When you finally click on setFirst , you run setNumber . This setNumber it does NOT update the number inside setFirst room, but it updates the number that will be used in the next render phase;

This example is to make you understand why, when you click on setSecond , you get logged 1 One : when the setSecond method was initialized for that render, number was still 1 .

When setter function in eventloop. there is a asynchronous function. You can let be a sync function. but mostly we are not recommanded do this, so i prefer using callback to do this.

setNumber(2, () => {
  console.log('');
})

Anti-Pattern The sync way

Promise.resolve().then(() => {
  setNumber(2);
  console.log(2);
})

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