First: What Even Is a Hook?
React hooks are functions. Yeah, that’s it. Functions that let you hook into React’s lifecycle and features from a functional component. Before hooks, you had to use classes to get access to state or lifecycle methods. Hooks let you do all that from a regular function component.
There are many hooks, the built-in ones like useState, useEffect, useRef, etc. But this isn’t some “introduce all of them at once” nonsense. This is about useState. Fine here is one liner for few of these.
When Do You Use Hooks?
When you're in a function component and you need to do something that used to require a class, like keeping state, accessing lifecycle, or storing instance values, that’s when you pull in hooks.
Why Would You Ever Need State?
If you want your UI to change based on interaction, you need state.
- Want a button to show a modal? State.
- Want to toggle a dropdown? State.
- Typing in a form field and want to store that value? State.
- Checkbox? Counter? Theme switcher? You guessed it: state.
Without the state, your component is just a static render. You might as well be writing HTML.
What is useState?
useState is a hook that lets you create and manage state in a functional component.
Here’s the syntax:
const [count, setCount] = useState(0);
- count is your state value.
- setCount is a function to update that value.
- 0 is the initial value of the state.
And no, you don’t mutate count directly. You always use setCount to update it. That’s one of the rules. More on that later.
When Do You Use It?
Whenever your component needs to remember something between renders and update the UI when that “something” changes.
Don’t use useState for stuff that:
- Doesn’t change (hardcoded configs? Just use constants).
- Doesn’t belong to the UI (like expensive calculations? Cache it or memoize).
- Belongs in global state or server state (go use useReducer, context, or React Query, depending on the case).
Let's start with the basics
I’m a button labeled ++. Every time you click, it increments a variable called count and log it to the console.
What does that show? It shows that the variable is updating, at least inside the function. But whether that update actually sticks around... that’s a whole different story.
const UseStateDemo = () => {
let count = 0;
function handleClick() {
count++;
console.log(count);
}
return (
<div>
<button onClick={handleClick}>++</button>
<h1>Check console</h1>
</div>
);
};
export default UseStateDemo;
![Check console]()
Let's print the count in UI
<h1>count = {count }</h1>
Let's see what's happening: Count is indeed updating its value as you could see it in console however it is not updating on UI. This is where we need useState hook/
![Count]()
So what is happening here?
let count = 0;
- This is a plain JavaScript variable.
- Every time the component runs (aka, re-renders), the count resets to 0.
- Even though you're calling count++, that change lives only inside handleClick and never reflects on the UI.
- So <h1> always shows: count = 0, no matter how many times you click.
Now add useState to the mix
const UseStateDemo = () => {
let [ count, setCount] = useState(0);
function handleClick() {
count++;
setCount(count)
}
return (
<h1>count = {count }</h1>
);
};
- Now we’re using React’s state system.
- useState(0) tells React: “Hey, I want a piece of state called count, and I’m starting it at 0.”
- setCount is a function React gives you to update the count properly.
So when the user clicks on the button, we fire the handleClick method. This increments the count and then calls setCount(count).
What setCount does:
- It tells React to update the value of count.
- and then re-render the component with the new value.
So now, when the component re-runs, React doesn’t reset the count to 0. It re-runs it with the updated value.
function handleClick() {
count++;
setCount(count);
console.log(count);
}
![components re-run]()
So we can also write logic inline.
<button onClick={() => {
count++;
setCount(count)
}}> ++ </button>
This is, however, not good to have an inline function, unless you have one one-liner.
<button onClick={() => setCount(count++)}>++</button>
Real-World Example: Toggle Button
const ToggleButton = () => {
let [isOpen, setToggle] = useState(false);
return (
<div>
<button onClick={ () => setToggle(!isOpen)}>
{isOpen ? "Show" : "Hide" }
</button>
{isOpen && <p> Woohoo, I am visible.</p>}
</div>
);
}
![]()
Even better approach?
Functional update form
<button onClick={ () => setCount(prev => prev+1))}>
You're not passing a value, you're passing a function to setCount. That function takes the previous state value (prev) and returns the new state value.
React guarantees that this function will get the latest value of state, even if multiple updates are queued.
But Why Use This Instead of just setCount(count + 1)?
Let’s say you write:
setCount(count++)
setCount(count++)
You think you’re incrementing twice, right? Wrong.
React batches state updates. If you're using count directly (from the render scope), it doesn’t update between calls. So both lines use the same count, and you only get +1 total.
![Increment]()
Now try,
setCount(prev => prev+1)
setCount(prev => prev+1)
Now it works. You get 2.
Because each call receives the latest value, not the one from the first render.
![First render]()
Conclusion
If you’re still thinking let count = 0 and console.log() is how you track state in React, then I’ve got news: you're not writing React, you're writing a confused JavaScript app pretending to be React.
You can console log all day. You can increment variables ‘til your finger cramps. But unless you’re telling React through hooks that something changed, it’s not gonna lift a damn finger.
So get it straight:
- Don’t mutate values, use the setter.
- Don’t rely on stale props or values; use the functional form when it counts.
- And stop thinking inline functions in JSX make you clever, unless it’s one-liner-clean, pull that logic out.
If your UI needs to reflect anything that changes, whether it’s a counter, a toggle, or a form field, useState is the one running the show.
You're welcome.