DEV Community

I.G
I.G

Posted on

How to update a value instantaneously instead of asynchronously in React

Overview

  • useState doesn't update a value instantaneously, so that value will be as it was even if it's accessed soon after useState like below.

cf. https://ja.react.dev/reference/react/useState#usage

  • React handles the accumulated queues and updates state in bulk after all events executing in parallel completed being done.
  • After that, components are re-rendered.

cf. https://ja.react.dev/learn/queueing-a-series-of-state-updates

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

  function handleClick() {
    setCount(count + 1);
    console.log(count); // 0
  }

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

useRef

  • Use useRef as "escape hatch" to get the latest updated state when setState is called in an asynchronous event(function).
Gist
  • setState is an asynchronous function.
  • The queue for updating the state is not captured by the lifecycle of the state in React when it is called in an asynchronous function. So the state is not updated then.

cf. https://crybabe.net/archives/12#toc1

cf. https://ja.react.dev/learn/referencing-values-with-refs#when-to-use-refs

function Example() {
  const [count, setCount] = useState(0);
  const [count2, setCount2] = useState(0);
  const countRef = useRef(0);

  const handleClick = () => {
    setTimeout(() => {
      setCount(count + 1);
      console.log(count); // 0
      setCount2(count => count + 1);
      console.log(count); // 1
      countRef.current += 1;
      console.log(countRef.current); // 1
    }, 1000);
  };

  const onSubmit = () => {
    console.log(count); // 0
    console.log(count2); // 0
    console.log(countRef.current); // 1
  };

  return (
    <div>
      <p>{countRef.current}</p>
      <button onClick={handleClick}>Click me</button>
      <button onClick={onSubmit}>Submit</button>
    </div>
  );
}

export default Example;
Enter fullscreen mode Exit fullscreen mode

However, you need to be careful about the point useRef is independent of the React rendering cycle and so doesn't update DOM.

In principle, it's important to design the overall logic in the component so that they can work even if useState runs asynchronously.

Functional update

  • Use a functional update way instead of the one to do directly if you wanna access an updated value soon after it's updated by useState in the same event(function).

cf. Reference article(ja)

const increaseDouble = () => {
  setCount(count + 1); // not done
  setCount(count + 1); // done
  console.log(count) // 1
};
Enter fullscreen mode Exit fullscreen mode
  1. First setCount(count + 1) is called. But count is still 0 at this point due to the asynchronous update.
  2. Instantaneously, second setCount(count + 1) is called. But count is still 0 at this point too because 1st setCount is not done yet.
  • So you can use a functional update to resolve that.

cf. https://ja.react.dev/reference/react/useState#updating-state-based-on-the-previous-state

const increaseDouble = () => {
  setCount(count => count + 1); // done
  setCount(count => count + 1); // done
  console.log(count) // 2
};
Enter fullscreen mode Exit fullscreen mode

※ However seems there's no big difference between a functional update and a direct update for the most part except for the case like above.

Top comments (0)