0

I am running into issues setting a state created with the 'useState' hook from within async functions.

I've created a codepen to demonstrate: https://codepen.io/james-ohalloran/pen/ZdNwWQ

const Counter = () => {
  const [count, setCount] = useState(0);

  const increase = () => {
    setTimeout(() => {
      setCount(count + 1);
    },1000);
  }

  const decrease = () => {
    setTimeout(() => {
    setCount(count - 1);
    },1000)
  };
  return (
    <div className="wrapper">
      <button onClick={decrease}>-</button>
      <span className="count">{count}</span>
      <button onClick={increase}>+</button>
    </div>
  );
};

In the above example, if you click 'increase' followed by 'decrease'..you will end up with -1 (I would expect it to be 0). If this was a React class instead of a functional component, I would assume the solution would be to use bind(this) on the function, but I didn't expect this to be an issue with arrow functions.

James
  • 2,951
  • 8
  • 41
  • 55

2 Answers2

0

It is because of using setTimeout

Let's assume that you've called the increase() 10 times in a second.

count will be always 0. Because the state is updated after a second, every increment() called in a second will have an unupdated count.

So every increment() will call setCount(0 + 1);.

So no matter how many times you call in a second, the count is always 1.

bkm412
  • 1,047
  • 5
  • 11
  • Yeah I can confirm this is the behaviour I'm seeing when I log out count. How would you suggest restructuring it so that the code in the setTimeout callback can access the new state? – James Jul 15 '19 at 11:19
0

Ah, I found a solution. I didn't realize I'm able to reference the previousState from the useState setter function: https://reactjs.org/docs/hooks-reference.html#functional-updates

James
  • 2,951
  • 8
  • 41
  • 55