State, the box that remembers
Props come from above. State lives inside the component. The first time you call useState, you have officially crossed from "I render stuff" to "I build apps".
useState in one paragraph
useState(initial) gives you back [value, setter]. The value is what to render. The setter, when called, tells React, "the value changed, please re-run this component". That re-run is a re-render. That is the whole thing.
function CartButton() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Cart ({count})
</button>
);
}The three rules
- The setter triggers a re-render. Reassigning
count = count + 1does nothing. You must callsetCount. - State is immutable. Never
state.items.push(x). Make a new array:setItems([...items, x]). React compares by reference; mutating in place looks the same to it and the screen will not update. - Updates are batched. Calling
setCount(count + 1)three times in one handler increments by one, not three. Use the function form when the new value depends on the old:setCount(c => c + 1).
The mutation rule trips up every Java or Python dev once. The first time I hit this bug, I was three hours into a Swiggy clone and my cart count just refused to budge.
Multiple useState vs one object
Most of the time, multiple useState calls win. Cleaner, easier to read, no spread-merge gymnastics.
// Good
const [name, setName] = useState('');
const [city, setCity] = useState('Bengaluru');
// Only when fields move together
const [form, setForm] = useState({ name: '', city: 'Bengaluru' });
setForm(f => ({ ...f, name: 'Riya' })); // spread the restIf two pieces of state always change together (form fields, coordinates), grouping is fine. Otherwise, split.
The derived-state newbie bug
You have an array of orders. You want the total. The instinct is wrong:
const [orders, setOrders] = useState([]);
const [total, setTotal] = useState(0); // bug magnetNow every place that updates orders must remember to update total. It will get out of sync by Friday.
Just calculate it. Every render. It is cheap.
const total = orders.reduce((sum, o) => sum + o.price, 0);Rule of thumb: if you can compute it from existing state, do not store it. State is for things React cannot derive on its own.
Quick gotcha list
- State updates are async.
setCount(5); console.log(count);will log the old value. - Initial value runs every render but is only used once. For expensive setup, pass a function:
useState(() => heavyCalc()). - Setting the same value skips the render. React bails out if
Object.is(newValue, oldValue).
Next up: side effects, the part of React where most bugs are born.
Free tools you can use while you learn
Common questions
Q.Why is my state not updating immediately?›
Q.When should I use useState vs useReducer?›
Watching quietly. Tap me if you want a tip.
Try this (0 of 1 done)
- 1
Add a reset button that sets count back to 0.
show answer
function App() { const [count, setCount] = React.useState(0); return ( <div> <button onClick={() => setCount(count + 1)}>Clicked {count} times</button> {' '} <button onClick={() => setCount(0)}>reset</button> </div> ); }