How useState works in React.js (Rendering, Asynchronous, Batch and more)

How useState works in React.js (Rendering, Asynchronous, Batch and more)

I expect that you have basic knowledge of React.js and Hooks. I will more focus on under the hood and some interesting behaviour of useState in this blog.

Hooks are really useful in functional components in React.js. useState hook is commonly used. In this blog, we will discuss about useState hook in depth.

useState is a React Hook that adds a state variable to your component. In easy words, state is used to store any data and useState hook helps to do it.

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const countHandler = () => {
    setCount(count + 1);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={countHandler}>Press Me</button>
    </div>
  );
}
export default App;

useState hook returns an array containing a pair of values. The first value is "current value" and the second value is "state value update function". When this function is invoked, the state value will be updated.

Here, count is the current value and setCount is the state update function. We can declare the initial state value for the first rendering. Here initial value is 0 (useState(0)).

Now, a question can come to your mind why do we need this type of state management? We can declare a variable and store the value instead of using useState.

Let's feel the problem. Jump in the code. Follow the bellow code.

function App() {
  var count = 0;
  const countHandler = () => {
    count++;
    console.log(count);
  }

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={countHandler}>Press Me</button>
    </div>
  );
}

Now click on the button and try to understand what happened here. When you click the button nothing will change on the screen but count value is changing.

Can you find the problem? The component is not rendering here. This is the reason, nothing is changed on the browser screen.

Now, we understand that we have to use useState . But why don't we update the state directly?

useState has a very deep relationship with rendering. When the state is updated, the component and its all child will be rendered.

Let me be clear, the rendering does not mean updating the DOM. When the rendering happens, our virtual dom is updated. React has a new virtual dom and a copy of the old virtual dom. React engine compares the old and new virtual dom and figures out the updated part. Then the real dom will update.

Back to the previous question. Follow this code

import { useState } from 'react';

function App() {
  let [count, setCount] = useState(0);
  const countHandler = () => {
    count = count + 1;
    console.log(count)
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={countHandler}>Press Me</button>
    </div>
  );
}

export default App;

Nothing is changed when we click the button but count value is increasing. The problem is the same, rendering is not triggered here. We must have used setState to achieve the expected output.

Now, another question is how setState triggered the rendering.

When we try to update the state value using setState , setState compare the value with the current state value. If these values are different, then it creates a new object reference for the new value. Then state understand value is updated and the component should be rendered. If we update the state directly, no new object reference is created, and react also doesn't it should render the component.

Look at the bellow code. We will discuss another interesting fact.

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const [toatlCount, setTotalCount] = useState(0);
  const countHandler = () => {
    setCount(count + 1);
    setTotalCount(count + 1);
  };

  return (
    <div>
       {console.log("I am rendering")}
      <p>You clicked {count} times</p>
      <p>Total Count: {toatlCount}</p>
      <button onClick={countHandler}>Press Me</button>
    </div>
  );
}

export default App;

According to the above code, when will click on the button once, count value should be 1 and totalCount should be 2. In the console of the browser, I am rendering should be printed twice. Because setState is triggered the rendering.

But nothing happens like this. Run the code.

Why totalCount value, not 2? What happened here?

Here, setState behave like asynchronous. We know, when the state is changed, the component is rendered. Now, this re-rendering is expensive. React wait for any other state to be updated or not. Then it batched all the state action and updated once. Here two states updated in the same event, it batched all the state action. this is the reason, count the state is not updated instantly and totalCount the state is not updated as per our expectations.

Now, follow the other code.

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const countHandler = () => {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={countHandler}>Press Me</button>
    </div>
  );
}

export default App;

Can you guess what happened here? When we click the button, the value will increase by 1. But why? Because the last call of setState will override any previous value during batching.

Now, try to understand how can we solve this problem. Let's jump to the code.

import { useState } from 'react';

function App() {
  const [count, setCount] = useState(0);
  const countHandler = () => {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  };
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={countHandler}>Press Me</button>
    </div>
  );
}

export default App;

Now we get our expected output. Here. prev holds the value of the state before setStateis triggered. This is very important when we want to update the state based on the previous state value.

Hopefully, you understand the concept and some interesting behavior.

Happy Coding!