Skip to content Skip to sidebar Skip to footer

When Are Functional Updates Required For Computations Involving Previous State?

According to the documentation for the useState React Hook: If the new state is computed using the previous state, you can pass a function to setState. The function will receive

Solution 1:

The main scenarios when the functional update syntax is still necessary are when you are in asynchronous code. Imagine that in useEffect you do some sort of API call and when it finishes you update some state that can also be changed in some other way. useEffect will have closed over the state value at the time the effect started which means that by the time the API call finishes, the state could be out-of-date.

The example below simulates this scenario by having a button click trigger two different async processes that finish at different times. One button does an immediate update of the count; one button triggers two async increments at different times without using the functional update syntax (Naive button); the last button triggers two async increments at different times using the functional update syntax (Robust button).

You can play with this in the CodeSandbox to see the effect.

importReact, { useState, useEffect } from"react";
importReactDOM from"react-dom";

functionApp() {
  const [count, setCount] = useState(1);
  const [triggerAsyncIndex, setTriggerAsyncIndex] = useState(1);
  const [triggerRobustAsyncIndex, setTriggerRobustAsyncIndex] = useState(1);
  useEffect(
    () => {
      if (triggerAsyncIndex > 1) {
        setTimeout(() =>setCount(count + 1), 500);
      }
    },
    [triggerAsyncIndex]
  );
  useEffect(
    () => {
      if (triggerAsyncIndex > 1) {
        setTimeout(() =>setCount(count + 1), 1000);
      }
    },
    [triggerAsyncIndex]
  );
  useEffect(
    () => {
      if (triggerRobustAsyncIndex > 1) {
        setTimeout(() =>setCount(prev => prev + 1), 500);
      }
    },
    [triggerRobustAsyncIndex]
  );
  useEffect(
    () => {
      if (triggerRobustAsyncIndex > 1) {
        setTimeout(() =>setCount(prev => prev + 1), 1000);
      }
    },
    [triggerRobustAsyncIndex]
  );
  return (
    <divclassName="App"><h1>Count: {count}</h1><buttononClick={() => setCount(count + 1)}>Increment Count</button><br /><buttononClick={() => setTriggerAsyncIndex(triggerAsyncIndex + 1)}>
        Increment Count Twice Async Naive
      </button><br /><buttononClick={() => setTriggerRobustAsyncIndex(triggerRobustAsyncIndex + 1)}
      >
        Increment Count Twice Async Robust
      </button></div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit Async state updates

Another possible scenario where functional updates could be necessary would be if multiple effects are updating the state (even if synchronously). Once one effect updates the state, the other effect would be looking at out-of-date state. This scenario seems less likely to me (and would seem like a poor design choice in most cases) than async scenarios.

Post a Comment for "When Are Functional Updates Required For Computations Involving Previous State?"