-1

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.

jmfirestone
  • 71
  • 1
  • 1

2 Answers2

1

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.

Jolly
  • 1,678
  • 2
  • 18
  • 37
  • Thanks for the detailed explanation. I got it. Since `setNumber()` is asynchornous, this is not reflected immediately in `console.log()` even though its in next line. If I call `setFirst()` again after some time, it shows the expected value. – jmfirestone Mar 25 '20 at 13:13
  • Yes. Though, I do wonder if, with *after some time*, you mean that you use `setTimeout`: if so, you shouldn't, but you should use `useEffect`. – Jolly Mar 25 '20 at 13:15
  • 1
    I just tried to see if my thought is right with `setTimeout()`. It worked. Then I did this. `useEffect(() => { console.log(currentPage) }, [currentPage]);` and it reflected the correct value – jmfirestone Mar 25 '20 at 13:22
  • Assuming `currentPage` is `number`, that is the right approach with React Hooks. You should always think ”*cause-effect*”: the *cause* is inside the dependencies array, while the *effect* is the function: you want that *when `currentPage` change, then I do **that***. So, you use `useEffect`, dividing the *cause* from the *effect*. – Jolly Mar 25 '20 at 13:27
0

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);
})
quanwei li
  • 354
  • 2
  • 16