Menu

React useState Hook: Examples and Common Mistakes

useState gives a component a value that survives re-renders and a setter that redraws the screen when the value changes. Learn the syntax, updater functions, objects and arrays in state, and why a value looks stale right after you set it.

This page includes runnable editors - edit, run, and see output instantly.

useState is the hook that lets a component remember something between renders: a count, the text in an input, whether a menu is open. Every time you call its setter, React renders the component again with the new value.

Click the button, then change useState(0) to useState(10) in the editor. The preview rebuilds and the count starts from 10.

The syntax

const [value, setValue] = useState(initialValue);
  • initialValue is used on the first render only. On every later render React ignores it and hands back the value it stored.
  • value is the current value for this render.
  • setValue replaces the value and schedules a new render.

The names are yours. useState returns a two-item array and the square brackets unpack it, so [isOpen, setIsOpen] and [email, setEmail] are the same pattern.

Call useState at the top level of your component, never inside a loop, a condition or a nested function. React matches each call to its stored value by the order of the calls, so that order must be the same on every render (see the rules of hooks).

Several pieces of state

A component can call useState as many times as it needs. Each call is its own independent value.

State is a snapshot

The setter does not change the variable in your hands. It tells React what the value should be in the next render. Until that render happens, the current one keeps seeing the old value.

Open the console under the preview and click once. The handler logs 0, the old value, and then the component logs a new render with 1. Nothing is wrong: count is a constant for the whole render, and the next render gets a fresh one.

Updater functions

Because of the snapshot, calling the setter three times with count + 1 adds one, not three: every call computes 0 + 1. Pass a function instead and React feeds each call the result of the previous one.

Use the updater form whenever the next value depends on the previous one and the update might not run straight from the latest render: several sets in one handler, a setInterval callback, or an effect.

Objects and arrays in state

React decides whether to render again by comparing the old and new value with Object.is. If you change an object in place and pass the same object back, the comparison says nothing changed and the screen stays as it was. Always pass a new object or array.

{ ...user, age: user.age + 1 } copies every field and overrides one. [...tags, 'code'] adds an item, and filter removes one, both returning a new array. The page on updating arrays and objects covers nested objects, editing one item in a list and inserting in the middle.

Lazy initial state

The argument to useState is evaluated on every render, even though React only uses it the first time. When computing it is expensive (reading localStorage, parsing a big string), pass a function. React calls it once, on the first render.

// Runs readSavedNotes() on every render, then throws the result away
const [notes, setNotes] = useState(readSavedNotes());

// Runs it once
const [notes, setNotes] = useState(readSavedNotes);
const [notes, setNotes] = useState(() => readSavedNotes());

Resetting state with a key

State belongs to a component's position in the tree. If you render a different key in the same place, React treats it as a new component and its state starts over. This is the cleanest way to reset a form when the thing it edits changes.

Type a message, then switch the recipient: the draft clears. Delete key={to} and try again, and the text stays, because React now sees the same Draft in the same place.

Common mistakes

Logging state right after setting it. You see the old value, as shown above. Log in the component body, or compute the next value into a variable first and use that.

Mutating state. user.age++; setUser(user) passes the same object, so nothing renders. Copy first.

Calling the setter during render. <button onClick={setCount(count + 1)}> calls setCount while rendering, which renders again, which calls it again. React stops this loop with "Too many re-renders". Pass a function: onClick={() => setCount(count + 1)}.

Storing values you can compute. If fullName is always first + ' ' + last, compute it during render instead of keeping a third piece of state that can fall out of sync.

Frequently Asked Questions

What does useState return?

An array with exactly two items: the current value and a function that replaces it. You name them yourself with array destructuring, by convention [thing, setThing].

Why does my state not update immediately?

Calling the setter does not change the variable you already have. It asks React to render the component again, and the new value appears in that next render. Inside the current event handler the old value is still in scope, so console.log(count) right after setCount(count + 1) prints the old number.

When should I pass a function to the setter?

When the new value depends on the old one and you set it more than once, or from inside a timer or an effect. setCount(c => c + 1) always starts from the latest value, while setCount(count + 1) starts from the value this render saw.

Can useState hold an object or an array?

Yes, but treat it as read only. Build a new object or array and pass that to the setter (setUser({ ...user, name }), setItems([...items, item])). Changing the existing one in place does not trigger a render.

What is the difference between useState and useReducer?

Both store state. useState suits a few independent values. useReducer moves the update logic into one function that receives an action, which reads better when many events change the same state in related ways.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED