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 setState
is 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!