useState
a value react remembers between renders. the catch is that it is a snapshot of the render that read it, not a variable you update in place.
the value is frozen for that render
useState gives you two things: the value as it was when this render started,
and a function that schedules the next render.
the first half is where everyone trips. this button adds 1:
<button onClick={() => setValue(value + 1)}>{value}</button>
and so does this one:
<button onClick={() => { setValue(value + 1); setValue(value + 1); }}>{value}</button>
both calls read the same value, the one captured when the component
rendered. with value at 0, both schedule "set it to 1". the second call never
sees the first.
the same thing bites anywhere that outlives the render: a setInterval, a
promise callback, a listener registered once. they all keep reading the
snapshot they closed over.
the updater form reads the current value
pass a function instead of a value and react hands you the state as it is at the moment the update is applied:
<button onClick={() => { setValue(v => v + 1); setValue(v => v + 1); }}>{value}</button>
now it adds 2.
if the next value depends on the previous one, use the function form. it costs nothing and removes the whole class of bug.
coming from plain javascript
it is tempting to read useState as a variable with extra steps:
let value = 1;
btn.addEventListener('click', () => { btn.textContent = ++value; });
here value really is one box everyone reads and writes. in react it is not,
every render gets its own. carrying this mental model over is what produces the
bugs above.
when not to use it
- the value can be computed from props or other state. compute it during render. state that duplicates something else goes out of sync, and now you own the sync.
- the value never reaches the screen. a timer id, a previous value you only
read inside a callback, that is
useRef. state re-renders, refs don't. - more than one component needs it. lift it up or put it in a store. two copies of the same state is the first bullet again, one level up.