Hey!
Let me ask you something before we start.
You built a counter in plain JavaScript. You clicked a button. The number went up. It worked.
So when you moved to React — you tried the same thing.
function Counter() {
let count = 0;
function handleClick() {
count = count + 1;
console.log(count); // this works fine
}
return (
<div>
<p>{count}</p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
You click the button. The console shows the number going up. But the screen? The screen stays at 0.
Nothing changes on the page.
And you wonder — what is going on?
That confusion is exactly why useState exists. And by the end of this blog, you will fully understand it.
1. Why Does the Screen Not Update?
Here is the thing about React.
React does not watch your normal variables. When you change let count = 0 — React has no idea that anything changed. So it does not re-render the component. The screen stays the same.
Think of it like this.
Imagine you are writing numbers on a whiteboard. You erase 0 and write 1. But the camera recording the whiteboard does not know you changed it — because nobody told the camera to look again.
React is the camera. Your variable change is you erasing and writing. React needs to be told — "hey, something changed, please look again and update the screen."
That is exactly what useState does.
2. What Is useState?
useState is a React hook that does two things:
- It stores a value (your state)
- It gives you a function to update that value — and when you use that function, React automatically re-renders the component and updates the screen
That is it. That is the whole idea.
Let us see the syntax first.
import { useState } from "react";
const [value, setValue] = useState(initialValue);
Let us break this down piece by piece.
useState(initialValue) — you call useState and pass the starting value. This can be a number, string, boolean, array, object — anything.
value — this is the current state. Whatever is stored right now.
setValue — this is the function to update the state. When you call this, React updates the value AND re-renders the component.
const [value, setValue] — this is called array destructuring. useState returns two things in an array. You unpack them into two separate variables in one line.
Quick question for you.
Can you name the two things useState always gives you back?
The current value. And the function to update it. Always. Every time.
3. Fix the Counter — Using useState
Now let us fix the broken counter from the beginning.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Click me</button>
</div>
);
}
Now when you click the button:
-
setCount(count + 1)is called - React updates
countto the new value - React re-renders the component
- The screen shows the updated number
The screen actually changes now. That is the difference.
Let us trace through exactly what happens step by step:
Page loads
→ count = 0
→ Screen shows: Count: 0
User clicks button
→ handleClick runs
→ setCount(0 + 1) is called
→ React updates count to 1
→ React re-renders Counter
→ Screen shows: Count: 1
User clicks again
→ setCount(1 + 1) is called
→ React updates count to 2
→ Screen shows: Count: 2
Every click triggers a re-render. Every re-render shows the latest value. That is useState working.
4. The Naming Convention
Before we go further — notice the naming pattern.
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const [isOpen, setIsOpen] = useState(false);
The update function always starts with set followed by the state name.
count and setCount.
name and setName.
isOpen and setIsOpen.
This is not a rule enforced by React. It is a convention the whole React community follows. Stick to it — your code will be easier to read.
5. Example 1 — Toggle Show and Hide
Let us build something more practical.
A button that shows and hides a message.
import { useState } from "react";
function ToggleMessage() {
const [isVisible, setIsVisible] = useState(false);
function handleToggle() {
setIsVisible(!isVisible);
}
return (
<div>
<button onClick={handleToggle}>
{isVisible ? "Hide Message" : "Show Message"}
</button>
{isVisible && <p>Hello! I am visible now.</p>}
</div>
);
}
Quick question for you.
What does !isVisible do?
If isVisible is true — !isVisible makes it false.
If isVisible is false — !isVisible makes it true.
It flips the value every time. One click shows. Next click hides. Next click shows again.
And the button text changes too — {isVisible ? "Hide Message" : "Show Message"}. That is a ternary operator. If visible, show "Hide Message". If not, show "Show Message".
The UI reacts to the state. That is why it is called React.
6. Example 2 — Input Field
This one is used in almost every real React app.
Capturing what a user types in an input field.
import { useState } from "react";
function NameInput() {
const [name, setName] = useState("");
function handleChange(event) {
setName(event.target.value);
}
return (
<div>
<input
type="text"
placeholder="Enter your name"
value={name}
onChange={handleChange}
/>
<p>Hello, {name}!</p>
</div>
);
}
Every time you type a letter:
-
onChangefires -
handleChangeruns -
event.target.valuegets the current text in the input -
setNameupdates the state - React re-renders
- The
<p>below shows the updated name instantly
Type "R" — screen shows "Hello, R!"
Type "Ra" — screen shows "Hello, Ra!"
Type "Ravi" — screen shows "Hello, Ravi!"
This pattern is called a controlled input. The input's value is always controlled by React state. It is the standard way to handle forms in React.
7. Example 3 — Like Button
Something you have seen on every social media app.
import { useState } from "react";
function LikeButton() {
const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState(0);
function handleLike() {
if (!liked) {
setLiked(true);
setLikeCount(likeCount + 1);
} else {
setLiked(false);
setLikeCount(likeCount - 1);
}
}
return (
<div>
<button onClick={handleLike}>
{liked ? "Unlike" : "Like"}
</button>
<p>{likeCount} Likes</p>
</div>
);
}
Notice something here — one component has two useState calls.
const [liked, setLiked] = useState(false);
const [likeCount, setLikeCount] = useState(0);
You can use as many useState hooks as you need in one component. Each one is completely independent.
liked tracks whether you liked or not.
likeCount tracks the number.
Click once — liked becomes true, count goes up by 1.
Click again — liked becomes false, count goes down by 1.
8. Example 4 — Background Color Changer
Let us do something visual.
import { useState } from "react";
function ColorChanger() {
const [color, setColor] = useState("white");
return (
<div style={{ backgroundColor: color, padding: "40px", textAlign: "center" }}>
<p>Current color: {color}</p>
<button onClick={() => setColor("tomato")}>Red</button>
<button onClick={() => setColor("steelblue")}>Blue</button>
<button onClick={() => setColor("mediumseagreen")}>Green</button>
<button onClick={() => setColor("white")}>Reset</button>
</div>
);
}
State here is a string — the color name.
Every time you click a button, setColor updates the state to a new color string. React re-renders. The background changes instantly.
This shows that state is not just for numbers and booleans. It can hold any value — strings, arrays, objects — anything your UI needs to track.
9. One Important Rule — Never Mutate State Directly
This is a mistake many beginners make.
// WRONG — never do this
count = count + 1;
// WRONG — never do this either
myArray.push(newItem);
// RIGHT — always use the setter function
setCount(count + 1);
// RIGHT — for arrays, create a new array
setMyArray([...myArray, newItem]);
Why?
Because if you change state directly — React does not know it changed. No re-render happens. The screen does not update.
Always use the setter function. Always. That is the only way React knows something changed and needs to re-render.
10. What Happens When State Changes — The Full Picture
Let us put it all together.
Component renders for the first time
→ useState gives you the initial value
→ Component displays that value on screen
User does something (click, type, hover)
→ You call the setter function with a new value
→ React schedules a re-render
React re-renders the component
→ useState now gives you the updated value
→ Component displays the new value on screen
User does something again
→ The cycle repeats
This cycle — render, interact, update, re-render — is the core of how React works. And useState is what drives it.
Quick Summary — 5 Things to Remember
Normal variables do not trigger re-renders — React does not watch them. Changing a normal variable does nothing to the screen.
useState stores a value and gives you a setter —
const [value, setValue] = useState(initialValue). The setter is the only right way to update state.Calling the setter re-renders the component — React updates the value and redraws the component automatically.
You can use multiple useState in one component — each one is independent. Use as many as your component needs.
Never update state directly — always use the setter function. Direct mutation skips React and the screen will not update.
useState is the first hook every React developer learns. And once it clicks — the way you think about building UIs changes completely.
You stop thinking about DOM manipulation. You start thinking about state — what data does this component need to track, and how should the UI look based on that data.
That shift in thinking is what React is really about.
Try building these examples yourself. Change the initial values. Add new buttons. Break things and fix them. That hands-on time will teach you more than any blog can.
If you have a question or something did not click — drop it in the comments below. Happy to explain it differently.
Thanks for reading. Keep building.
Top comments (0)