Understanding the State in React
Let us start with the WHY.
Why do we need state?
When a component is mounted first, it displays data on the screen. And when an interaction causes a change in data, we expect the component should re-render with the updated value.
Consider the following example. We have a counter component, which displays a count and an 'Increment' button to increase the count.
function Counter() {
var count = 0;
function handleCount() {
count = count + 1;
}
return (
<div>
<span> { count } </span>
<button onClick={handleCount}> Increment </button>
</div>
);
}
But when we click on the Increment button, the count remains 0.
2 Issues with the current approach:
We are updating the local variables ( count ). But local variables do not persist between re-renders. When react renders, it renders it from scratch.
We don't have a mechanism of communication to React for triggering a re-render.
And the solution to the above problems is the State. We can refer to the state as a component's memory. State variables persist between re-renders and we have a state setter function which tells React to trigger a re-render.
Consider the following updated code:
import { useState } from "react";
function Counter() {
const [ count, setCount ] = useState(0);
function handleCount() {
setCount(count + 1);
}
return (
<div>
<span> { count } </span>
<button onClick={handleCount}> Increment </button>
</div>
);
}
We use the 'useState' hook from React.
When we call the useState function with an initial value ( initial value for the state variable ), we can destructure the returned value from it as a state variable ( count ) and state setter function ( setCount ).
Now, when we click on the Increment button, we see the count increment. Let us understand what is happening behind the scenes step by step.
Step 1. We click on the Increment button.
Step 2. It calls our handleCount function.
Step 3. Inside the handleCount function, we call the setCount function as
setCount(count + 1); // count initially was 0 so setCount(0 + 1)
Step 4. When setCount gets called, it tells React to trigger a re-render. One important point is React does not trigger until all the code in the event handler has run.
handleCount() {
setCount(count + 1);
// do something
// do something else
}
Here until the whole handleCount function has run, React does not trigger a re-render.
Step 5. After a re-render has been triggered, React re-renders our component. And by re-render we mean React calls our functional component in our case the Counter function. During the re-render, React calculates the value for the state for the next particular render in our case it is 1. So when React calls Counter, we execute all the code inside it line by line.
Step 6. Executing the Counter function
when the first line executes, the useState returns us the state for our current render i.e. count = 1;
Then Counter returns a JSX containing the updated value. Finally react constructs React DOM and updates the current DOM to match the React DOM
(reconciliation process) and we can see the reflected change in count value on the browser.
That is how we get updated value on the re-render. And this answers why state is needed in React.
State is local and private. If you render the same component twice, each copy will have a completely isolated state!
Each particular render has its associated snapshot of state. To learn more about this please refer to: https://react.dev/learn/state-as-a-snapshot
Now that we have understood the basics of how the state works, let's dive deeper into it.
import { useState } from "react";
function Counter() {
const [ count, setCount ] = useState(0);
function handleCount() {
setCount(count + 1);
}
return (
<div>
<span> { count } </span>
<button onClick={handleCount}> Increment </button>
</div>
);
}
Although it looks like the state variable 'count' is inside the function Counter, it is not where it lives. The state lives inside React.
React maps each piece of state to its associated component, by considering the component's position in the UI tree. What do I mean by this?
Consider the following component.
import { useState } from "react";
function Container() {
const [color, setColor] = useState('blue');
function handleColor() {
if(color == 'blue') setColor('red');
else setColor('blue');
}
return (
<div>
{ color == 'blue' ? <Counter color="blue" /> :
<Counter color="red" />
}
<button onClick={handleColor}> Change Color </button>
</div>
)
}
function Counter({ color }) {
const [ count, setCount ] = useState(0);
function handleCount() {
setCount(count + 1);
}
return (
<div>
<span style={{color: color}}> { count } </span>
<button onClick={handleCount}> Increment </button>
</div>
);
}
Here we are having two Counters and they are being rendered conditionally.
Click on the 'Increment' button and then click on the 'Change color' button.
We notice that the counter value is not reset to 0 even though the color has been changed to red.
So what is happening here?
We have 2 Counter components in the Container component, so shouldn't we have a separate state for each Counter component and counter should have been reset to 0 when <Counter color="blue" /> is replaced with <Counter color="red" />?
The answer to this lies in how React sees the Counter in the UI tree.
When <Counter color="blue" />, the counter is seen as the first child of the div of the Container Component and the same is when the <Counter color="red" />.
From React's perspective same component is at the same position in the UI tree, so React preserves the component state and that is why the counter value is not being reset even after we change the colour.
Hope this helped you in gaining clarity about how the state works in React.
As a closing remark, I would like you all to go through https://react.dev/learn/queueing-a-series-of-state-updates which tells you about how React batches state updates.
I would request you all to share it among your network and I would love to receive feedback from you all.
Thank You!!