React Hooks Series

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

01
The problem useState solves

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.

02
The signature
const [state, setState] = useState(initialValue);

// state     → the current value
// setState  → function to update the value
// useState  → takes one argument: the initial state
The array destructuring names are completely up to you. Convention is [value, setValue] — descriptive names make your intent obvious at a glance.

02 — How to use it

03
Primitive state — a counter

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);
04
String state — controlled input

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} />
05
Multiple state variables

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

06
The Rules of Hooks
  • 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.
07
Common pitfalls
PitfallWhy it happensFix
Stale state in handlerClosure captures old valueUse functional updater: prev => prev + 1
Object mutationReact checks reference, not deep equalitySpread: setState({ ...state, key: val })
Array mutationpush/splice mutate in placeUse [...arr, newItem] or .filter()
Expensive initial valueComputed on every renderLazy init: useState(() => compute())

04 — Live demo

StateTutorial — interactive

The component below is running live. The counter uses the safe functional updater form. The input is fully controlled — React owns the value.

Counter

0

Controlled input

Yaseen

05 — Quick reference

PatternCode
Declareconst [val, setVal] = useState(init)
Update (direct)setVal(newValue)
Update (from prev)setVal(prev => prev + 1)
Update objectsetVal(prev => ({ ...prev, key: x }))
Update array (add)setVal(prev => [...prev, item])
Update array (remove)setVal(prev => prev.filter(…))
Lazy initialiseruseState(() => expensiveCompute())
Next in the series: useEffect — running side-effects after render, controlling when they fire, and cleaning up after yourself.