useState & the re-render model

[check: …]
📖 Pair this live React playground with the companion guide (.md) — this page is the rendered ground truth.

State: a component's private memory

A plain local variable fails two ways: it is reset on every render, and mutating it never asks React to paint. useState fixes both. It returns a pair [value, setValue]: value is React's memory for this component instance (it survives re-renders), and setValue tells React "re-render me with this new value" — it does NOT mutate the variable in place. The render loop: render user clicks setX(next) React re-renders fresh JSX snapshot paint.
piecerolewhat it does
useState(0) declare state gives this instance a memory slot; first render returns 0
count (1st of the pair) the snapshot the value for this render; it never changes mid-render, even after setCount
setCount (2nd of the pair) request a re-render queues a new value; React re-renders and hands the next render the new value
setCount(c => c+1) functional update computes from the pending state; safe to call several times in one handler (they queue up)

React 19.2.7 (esm.sh) + Babel classic — the same CDN setup as react_via_cdn. Edit the JSX below and hit compile & render.

1 · the Counter you write (edit me)

2 · Babel compiles JSX into an element tree

<Counter/> becomes React.createElement(Counter) — a plain object describing the node. React walks that tree and produces real DOM. Each setCount/setOn asks React to call Counter again and paint a new snapshot.

// (hit “compile & render” to see Babel’s output here)

3 · live React state (the re-render model, proven)

Click the buttons in the card — that is live React state. The gold-check proves it end-to-end: it programmatically clicks +1 5 times (real click() events) and asserts the rendered count is "5"; then it clicks +3 once and asserts "8". The number you see below IS the gold value — it changed because React re-rendered on each click.

rendered count:  ·  toggle:  ·  gold clicks: 5 × +1, then 1 × +3 → expect count 8

intent → pattern (the cheat sheet, live)

intentpatternwhy
add 1 from the previous value setCount(c => c + 1) functional update uses the pending state; survives batching
set to a brand-new value setName('Robin') next value doesn't depend on the old one
flip a boolean (toggle) setOn(o => !o) negate the pending value
reset to initial setCount(0) or remount with a new key to wipe all state at once