29
What is useState, and why dont we use normal let?
Nowadays, we are still using the useState hook to set a variable in a React component. The useState, is introduced as 'hooks', is written like this
const [count, setCount] = React.useState<number>(0);
But what really is this? Why do we have to use this hook just to set a variable that holds a number and get incremented?
Why don't we just use something like this?
let count = 0;
count++;
Well, it always works in our first counter app with Vanilla JavaScript. Why don't we use it on React then?
React does a re-render by calling the component function, and with every function calls, your variable will get reset every single time.
Before we jump into the React Core Concept, let's step back to Vanilla JavaScript. For this demo, we are going to build a simple counter app.
let count = 0;
function add() {
count++;
document.getElementById('count').textContent = count;
}
Simple right? When the button—that has
add()
as a click listener—triggers, we add the count and update the text by accessing the documents.If we look closely, we can see that it is doing 3 actions. Let's break it down into its own functions.
// Declare
let count = 0;
function mutate() {
count++;
}
function render() {
document.getElementById("count").textContent = count;
}
// event listener pseudocode
when button is clicked:
mutate()
render()
And we get something like this:

Video Alt:
mutate()
and render()
.Before we continue, we have these 3 actions that we break down earlier:

Let's split the button into its own functions, so you can see it clearly.
<h1>Counter</h1>
<p id="count">0</p>
<button onclick="mutate()">Mutate</button>
<button onclick="render()">Render</button>
<script>
let count = 0;
function mutate() {
count++;
logTime();
console.log('clicking, count: ', count);
}
function render() {
document.getElementById('count').textContent = count;
}
</script>

Video Alt:
By bluntly translating the JavaScript code, this is what we have now.
function Component() {
let count = 0;
function mutate() {
count = count + 1;
console.log(count);
}
return (
<div>
<h1>{count}</h1>
<button onClick={mutate}>Mutate</button>
</div>
);
}
Do you see something odd?
found it?
Yes, there is no render function.
We can, of course, use the same render function by accessing
document
, but it's not a good practice to access them manually on React, our purpose of using React is not to manage them manually.What is the equivalent of render function in React then?
It is actually the
function Component()
itself.Whenever we want to update the screen, React are calling
Component()
function to do that.By calling the function, the
count
is declared again, the mutate
function is also re-declared, and at last, will return a new JSX.Here is the demo:

Video Description:
If we run the code with let on React, there will be no changes. That's because the render function doesn't get called.
React will trigger render function:
The second and the third are basically triggered because of setState too but in the parent element.
At this point, we know that every time the useState value changes, it will call the render function which is the Component function itself.
Before we convert the
count
variable to state, I want to demonstrate by creating a render function simulation, which uses setToggle. We can trigger re-render with render
now.function Component() {
//#region //*=========== Render Fn Simulation ===========
const [toggle, setToggle] = React.useState<boolean>(false);
function render() {
setToggle((t) => !t);
}
//#endregion //*======== Render Fn Simulation ===========
let count = 0;
const mutate = () => {
count = count + 1;
console.log(`${getTime()}| count: ${count}`);
};
return (
<div>
<h1>{count}</h1>
<Button onClick={mutate}>Mutate</Button>
<Button onClick={render}>Render</Button>
</div>
);
}
Let's see it in action

Video Alt:
This is actually because we are re-declaring the count variable.
function Component() {
//#region //*=========== Render Fn Simulation ===========
const [toggle, setToggle] = React.useState<boolean>(false);
function render() {
setToggle((t) => !t);
console.log(`${getTime()} | Render function called at count: ${count}`);
}
//#endregion //*======== Render Fn Simulation ===========
let count = 0;
const mutate = () => {
count = count + 1;
console.log(`${getTime()}| count: ${count}`);
};
return (
<div>
<h1>{count}</h1>
<Button onClick={mutate}>Mutate</Button>
<Button onClick={render}>Render</Button>
</div>
);
}
Every time react calls the Component function, we are re-declaring the count to be 0. The render function still works, and react updated the screen, but it updated to the re-declared count which is still 0.
Now that is why we can't use a normal variable in a React component.
You might also ask:
Why don't we move the declaration outside the Component function?
Well, it makes sense, by moving the declaration we are avoiding the
count
being re-declared to 0. Let's try it to be sure.let count = 0;
function Component() {
//#region //*=========== Render Fn Simulation ===========
const [toggle, setToggle] = React.useState<boolean>(false);
function render() {
setToggle((t) => !t);
console.log(`${getTime()} | Render function called at count: ${count}`);
}
//#endregion //*======== Render Fn Simulation ===========
const mutate = () => {
count = count + 1;
console.log(`${getTime()}| count: ${count}`);
};
return (
<div>
<h1>{count}</h1>
<Button onClick={mutate}>Mutate</Button>
<Button onClick={render}>Render</Button>
</div>
);
}

Video Alt:
count
is incremented to 3IT WORKS! or is it?
It did just work, that was not a fluke. But there is something you need to see.

Video Alt:
Yes, the variable doesn't get cleared.
This is not great behavior, because we have to manually clean it or it will mess up our app.
Now that is why we can't use a normal variable outside a React component.
This is the code if we are using useState
function Component() {
const [count, setCount] = React.useState<number>(0);
const mutateAndRender = () => {
setCount((count) => count + 1);
console.log(`${getTime()} | count: ${count}`);
};
return (
<div>
<h1>{count}</h1>
<div className='mt-4 space-x-2'>
<Button onClick={mutateAndRender} variant='light'>
Add
</Button>
</div>
</div>
);
}
And this is the demo

Video Alt:
You may notice that the console.log count is late by 1, ignore it for now.
So in recap, useState does 4 things:
const [count, setCount] = React.useState<number>(0);
setCount
const mutateAndRender = () => {
setCount((count) => count + 1);
console.log(`${getTime()} | count: ${count}`);
};
This is because the
setCount
function is asynchronous.After we call the function, it needs time to update the count value. So when we call the console.log immediately, it will still return the old value.
You can move the console.log outside of the function so it will run on re-render (
Component()
)function Component() {
...
const mutateAndRender = () => {
setCount((count) => count + 1);
};
console.log(`${getTime()} | count: ${count}`);
return ...
}

Here is the updated diagram, now you know what useState and setState do.
Great job, you have finished the first React Core Concept Series. I'll definitely continue this series as there are still many hooks to cover. Please hold on to the mental model that I put in this blog post, as I'm going to reference it again soon on the next post.
With this post, we learned that
See you in the next blog post. Subscribe to my newsletter if you don't want to miss it.
There is actually a pop quiz on my website, I suggest you take it to test your knowledge.
Here is the link to the quiz
Originally posted on my personal site, find more blog posts and code snippets library I put up for easy access on my site 🚀
29