React useState Explained for Beginners
October 27, 2026 · 2 min read
Use useState when a component needs to remember a value and update the screen when it changes. A plain variable can't do that, because React doesn't know it changed and runs your function from the top on every render.
A quantity counter, two ways
This component has a plain variable and a state variable side by side:
import { useState } from "react";
export default function Counter() {
let plain = 0;
const [qty, setQty] = useState(1);
return (
<div>
<button onClick={() => { plain = plain + 1; console.log("plain is now", plain); }}>
Plain: {plain}
</button>
<button onClick={() => setQty(qty + 1)}>Qty: {qty}</button>
</div>
);
}
Click Plain twice, then click Qty twice. The Console prints:
plain is now 1
plain is now 2
The buttons end up like this:
Plain: 0 | Qty: 3
The variable really changed (1, then 2), but the "Plain" button never left 0. The "Qty" button went from 1 to 3.
What is happening
useState(1)returns a pair: the current value (qty) and a setter function (setQty). The1is the initial value, used only on the first render.- Calling
setQty(qty + 1)stores the new value and asks React to render the component again. The new render gets the newqty. plainis recreated as0on every render, because a render is just a call to your function. Even when you change it, React isn't told, so nothing re-renders. And if a render did happen,let plain = 0would run again and wipe the change.
State is data a component remembers between renders. React keeps it outside your function and hands it back each time.
Common mistake
Calling the setter instead of passing it:
<button onClick={setQty(qty + 1)}>Qty: {qty}</button>
This runs setQty during the render, which triggers another render, forever. React stops it with "Too many re-renders". Fix it by passing a function that React calls on click:
<button onClick={() => setQty(qty + 1)}>Qty: {qty}</button>
Also don't write qty++ or qty = qty + 1. Always go through the setter.
Takeaway
If a value should show on screen and change over time, put it in useState and change it only with the setter. A normal variable resets on every render.