useState
The foundational hook for local component state. Every React developer reaches for useState first, understanding it deeply sets the baseline for every hook that follows.
01 — What it is
Before hooks, only class components could hold local state. Functional components were purely presentational — they took props and returned JSX. useState changed that entirely, letting you store and update values inside a function component without ever writing a class.
When the state value changes, React re-renders the component with the new value automatically. No manual DOM manipulation, no this.setState, no lifecycle methods.
const [state, setState] = useState(initialValue);
// state → the current value
// setState → function to update the value
// useState → takes one argument: the initial state[value, setValue] — descriptive names make your intent obvious at a glance.02 — How to use it
The simplest case: a number that increments on button click. Always prefer the functional updater form when the new value depends on the previous one.
const [counter, setCounter] = useState(0);
// ✓ safe — reads the latest value even when batched
const increment = () => setCounter((prev) => prev + 1);
// ✗ risky — can read stale value if updates are batched
const increment = () => setCounter(counter + 1);Controlled inputs store their value in state and update on every keystroke via onChange. The component is always the single source of truth — React drives the input, not the browser.
const [inputValue, setInputValue] = useState("Yaseen");
const onChange = (event) => {
setInputValue(event.target.value);
};
// Wire it up:
<input value={inputValue} onChange={onChange} />Call useState as many times as you need. Each call manages one independent piece of state — keep them small and focused rather than bundling everything into one object.
const [counter, setCounter] = useState(0);
const [inputValue, setInputValue] = useState("Yaseen");
const [isVisible, setIsVisible] = useState(true);03 — Rules & gotchas
- Only call hooks at the top level. Never inside loops, conditions, or nested functions — React relies on call order to track which state belongs to which hook.
- Only call hooks from React function components or your own custom hooks. Not from regular JS functions.
| Pitfall | Why it happens | Fix |
|---|---|---|
| Stale state in handler | Closure captures old value | Use functional updater: prev => prev + 1 |
| Object mutation | React checks reference, not deep equality | Spread: setState({ ...state, key: val }) |
| Array mutation | push/splice mutate in place | Use [...arr, newItem] or .filter() |
| Expensive initial value | Computed on every render | Lazy init: useState(() => compute()) |
04 — Live demo
The component below is running live. The counter uses the safe functional updater form. The input is fully controlled — React owns the value.
Counter
Controlled input
05 — Quick reference
| Pattern | Code |
|---|---|
| Declare | const [val, setVal] = useState(init) |
| Update (direct) | setVal(newValue) |
| Update (from prev) | setVal(prev => prev + 1) |
| Update object | setVal(prev => ({ ...prev, key: x })) |
| Update array (add) | setVal(prev => [...prev, item]) |
| Update array (remove) | setVal(prev => prev.filter(…)) |
| Lazy initialiser | useState(() => expensiveCompute()) |