Search code examples
reactjsreact-hooks

Why is React useState with functional update form is needed?


I'm reading React Hook documentation about functional updates and see this quote:

The ”+” and ”-” buttons use the functional form, because the updated value is based on the previous value

But I can't see for what purposes functional updates are required and what's the difference between them and directly using old state in computing new state.

Why is functional update form needed at all for updater functions of React useState Hook? What are examples where we can clearly see a difference (so using direct update will lead to bugs)?

For example, if I change this example from documentation

    function Counter({initialCount}) {
      const [count, setCount] = useState(initialCount);
      return (
        <>
          Count: {count}
          <button onClick={() => setCount(initialCount)}>Reset</button>
          <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
          <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
        </>
      );
    }

to updating count directly:

    function Counter({initialCount}) {
      const [count, setCount] = useState(initialCount);
      return (
        <>
          Count: {count}
          <button onClick={() => setCount(initialCount)}>Reset</button>
          <button onClick={() => setCount(count + 1)}>+</button>
          <button onClick={() => setCount(count - 1)}>-</button>
        </>
      );
    }

I can't see any difference in behavior and can't imagine case when count will not be updated (or will not be the most recent). Because whenever count is changing, new closure for onClick will be called, capturing the most recent count.


Solution

  • State update is asynchronous in React. So it is possible that there would be old value in count when you're updating it next time. Compare, for example, result of these two code samples:

    function Counter({initialCount}) {
      const [count, setCount] = useState(initialCount);
      return (
        <>
          Count: {count}
          <button onClick={() => setCount(initialCount)}>Reset</button>
          <button onClick={() => {
            setCount(prevCount => prevCount + 1); 
            setCount(prevCount => prevCount + 1)}
          }>+</button>
        </>
      );
    }
    

    and

    function Counter({initialCount}) {
      const [count, setCount] = useState(initialCount);
      return (
        <>
          Count: {count}
          <button onClick={() => setCount(initialCount)}>Reset</button>
          <button onClick={() => {
            setCount(count + 1); 
            setCount(count + 1)}
          }>+</button>
        </>
      );
    }