Detailed explanation
useState is a React Hook that lets a function component remember a value between renders and request a new render when that value changes. For example, const [n, setN] = useState(0) creates state initially equal to 0. The returned pair contains the current render’s state value and a setter function. Unlike a normal local variable, state is preserved by React across renders of the same mounted component. Call Hooks at the top level of components or custom Hooks, not inside conditions or loops.
Each render receives a snapshot of state. Calling setN does not change the n variable in an already-running event handler; it queues an update for React to process. This is why updates are often described as asynchronous, although the setter does not return a Promise and cannot be awaited to obtain the updated state. React batches updates to avoid unnecessary renders. In modern React roots, batching also applies to updates within promises, timers, and native event handlers, not just React event handlers.
When the next state depends on the previous state, pass an updater function: setN(p => p + 1). React processes queued updaters in order, passing each one the pending state produced by earlier updates. Starting from 0, two setN(n + 1) calls in the same handler both queue the value 1 because both read the same snapshot. Independently starting from 0, two functional updates produce 2. These examples are alternatives, not consecutive sequences. Updaters must be pure: do not mutate data or perform side effects inside them.
For objects and arrays, create new values rather than changing the existing state directly. For example, setUser(prev => ({ ...prev, name: 'Asha' })) preserves other fields while replacing name. A useState setter replaces the whole value; it does not automatically merge object properties. React can skip rendering when the next value is equal under Object.is. Interviewers look for snapshot reasoning, correct updater usage, immutable updates, and awareness that setters schedule changes rather than immediately rewriting local variables.
Key points to remember
- useState(initialValue) returns the current state and its setter.
- Each render has a state snapshot; calling a setter does not modify that snapshot.
- React queues and batches updates; setters do not return awaitable Promises.
- Use setState(prev => nextValue) whenever the next value depends on pending state.
- Replace objects and arrays immutably; useState does not merge object fields automatically.
Common mistakes candidates make
- Expecting console.log(state) immediately after a setter to show the next value.
- Making repeated snapshot-based updates when functional updaters are needed.
- Mutating an existing object or array and passing the same reference back to the setter.
Likely follow-up questions
When is passing a value directly to the setter correct?
Pass a value directly when it does not depend on previous state, such as setName(event.target.value) or setCount(0). Use an updater for operations such as incrementing, toggling, or appending to an existing array.
How do you update a nested object in state?
Copy every object along the path being changed, for example setUser(prev => ({ ...prev, address: { ...prev.address, city: 'Pune' } })). A spread creates only a shallow copy, so copying the outer object alone does not protect nested objects from mutation.
Why might an updater function run twice during development?
React Strict Mode may call updater functions twice in development to help detect impurities, discarding one result. A pure updater still produces the correct state, but side effects inside it could run twice.
Interview tip: Explain the state snapshot first, then contrast the two increment examples, explicitly starting each from zero.
