A button that counts clicks needs to remember its number between renders. A changing local variable is not enough: React needs to know when to display a new value. In a functional component, useState provides that connection. section 5.2
Count button clicks
import { useState } from "react"; function Counter() { const [counter, setCounter] = useState(0); return ( <main> <p>Clicks: {counter}</p> <button onClick={() => setCounter(value => value + 1)}> Increment </button> </main> ); } section 5.2
Pass an initial value to useState. It returns a two-element array: the current value and its setter. Array destructuring gives them useful names, commonly a value name and a matching set-prefixed name. Display the value in JSX; call the setter when an event should change it. The setter accepts either a new value or a function that calculates one from the previous value. section 5.2.3 section 5.6
A few practical rules keep state predictable:
- The initial value matters only when that component instance first mounts. It can be a constant or a property; a later property change does not automatically reset the state. section 5.2.2 section 6.1.4
- For an expensive initial calculation, pass an initializer function, such as useState(() => generatePassword()), so React calls it only when initialization is needed. To store a function itself, return it from an initializer. section 5.2.2
- Call hooks in the same order on every render. Put useState before a possible early return, not inside a condition. section 5.2.1
- Make updates in callbacks or other hooks, not directly in the component definition. Setting an unchanged value does not cause a re-render; mutating an object without replacing its reference will not trigger one. section 5.6 section 5.5
Keep separate concerns separate
A to-do component can hold its items and its visibility filter independently: const [todos, setTodos] = useState(initialList); const [hideDone, setHideDone] = useState(false); const filteredTodos = hideDone ? todos.filter(({ done }) => !done) : todos; Buttons can set hideDone to true or false. Completing an item instead requires a new array with the changed item. Use separate state values for unrelated concerns; group values when they are tightly related. section 5.2.6 section 5.2.4
A small local value, such as whether a menu is open, is a good fit. As state grows complex, a reducer, context provider, or custom hook may make it easier to manage; moving from useState to a reducer is a choice, not a requirement. section 7.1.1 section 13.7


