Asynchronous behavior of useState Hook

Asynchronous behavior of useState Hook

Have you wondered why sometimes the state in React is not updated immediately?

Sometimes you increment a counter state and still the state does not show the incremented value immediately.

Let's understand this behavior of the useState function by taking the below example:

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };
  return (
    <div className="App">
      <button onClick={handleClick}>Click me</button>
      <br />
      <h3>{count}</h3>
    </div>
  );
}

Here on clicking the Click me button, the count value is incremented only one time and the count value becomes 1. But, don't you think that in the above code, setCount is being called 4 times so the count value should increment to 4?

The answer is No. This is because React has a Batching Mechanism System which improves the performance of React applications by batching the state updates.

How does state batching work?

Before we jump into the working of state batching, let's understand the working of a state in React:

When you call the setCount function, React doesn't apply the state change immediately. Instead, it schedules the update to be processed asynchronously. Let's understand it by using three terms: Asynchronous Updates, Scheduling the Update, and Asynchronous Processing.

  • Asynchronous Updates: When you call the setCount function in React, it doesn't immediately change the state of the component. Instead, React handles state updates asynchronously, meaning that state updates don't happen right away in the same synchronous flow of your code.

  • Scheduling the Update: When you call setCount, React takes note of the state change request but doesn't execute it immediately. Instead, it schedules the state update to be processed later in a separate phase of the event loop.

  • Asynchronous Processing: The term "Asynchronous" means that the state update is processed independently of the current execution context. It allows React to optimize the handling of state updates and other operations without blocking the main thread of the application.

    Conclusion:

    1. Initially, the 'count' state is set to 0.

    2. When you click the button, the handleclick function is executed, and setCount(count + 1) is called. However, at this point, the state update is scheduled but not immediately applied.

    3. React proceeds to re-render the component, and during the re-render, the updated state is applied. The updated state then reflected in the rendered UI. Since the state update and the subsequent re-render happen quickly and in a single synchronous cycle, you see the updated value immediately in the UI. This is because of the React's efficient rendering process.

    4. In case of multiple state updates, use of functional form of setCount is recommended to ensure correct state transitions. Let's see in below example:

       const handleClick = () => {
         setCount((prevCount) => prevCount + 1);
         setCount((prevCount) => prevCount + 1);
         setCount((prevCount) => prevCount + 1);
         setCount((prevCount) => prevCount + 1);
       };
      

In the above example, we are using the functional form of setCount, you are passing a function that takes the previous state as an argument and returns the new state. This ensures that each call to setCount is based on the most recent state.

How state batching works:

let's take below example to understand:

import "./styles.css";
import { useState } from "react";

export default function App() {
  const [count, setCount] = useState(0);
  const handleClick = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };
  return (
    <div className="App">
      <button onClick={handleClick}>Click me</button>
      <br />
      <h3>{count}</h3>
    </div>
  );
}

Asynchronous Updates: When you call setCount, React doesn't apply the state change immediately. Instead, it schedules the update to be processed asynchronously(already discussed in working of state in react).

Batching: If there are multiple setCount, calls within the same synchronous execution block(for example, within the same event handler or lifecycle method), React batches them together.

Update Merging: React then merges multiple state updates into a single update. This means that even if you call setCount multiple times, React will only consider the latest state, and the component will be re-rendered based on the final state value or you can say that React will perform a single re-render.

This is because useState function is asynchronous and React plays an important role here. React's nature is to prevent unnecessary re-renders to make your App optimized and fast. And how React does this is, by creating batches of state updates. This means, it only performs the state update once with the latest value of the state variable or you can say React updates the count value only by executing the last setCount function. This is for the performance reasons.

Here what is happening in the example above, the previous three calls to setCount function are overwritten by the last function call, and all these calls are made before the React re-renders the component.