React useState Hook
Manage local component state, functional updates, lazy initial state, and immutable updates with useState.
TL;DR
- Store interactive component values across renders using the
useStatehook. - Destructure current state and its dedicated
setterdispatch function. - Queue sequential updates reliably by providing an updater
callbackfunction.
Basic State Declaration
Primitive StateDeclare a boolean or numeric value with initial default.
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
const inc = () => setCount(count + 1);
return <Button onClick={inc}>{count}</Button>;
}String Input StateTrack text input values synchronized with DOM events.
const [name, setName] = useState('');
<InputBase
value={name}
onChange={(e) => setName(e.target.value)}
/>Boolean ToggleInvert boolean state with concise negation logic.
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen((prev) => !prev);Functional Updates
Sequential IncrementsQueue multiple consecutive updates safely without race bugs.
function incrementThreeTimes() {
setCount((c) => c + 1);
setCount((c) => c + 1);
setCount((c) => c + 1);
}Async Callback StateSafely read latest state inside delayed timeouts.
setTimeout(() => {
setScore((prev) => prev + 10);
}, 1000);Lazy InitializationExecute slow computation only during the initial mount.
const [data] = useState(() => {
const raw = localStorage.getItem('key');
return JSON.parse(raw ?? '{}');
});Updating Objects and Arrays
Object Field SpreadSpread existing object keys and override targeted property.
const [user, setUser] = useState({ name: '', age: 0 });
setUser((prev) => ({ ...prev, name: 'Maya' }));Append Array ItemCreate a new array copy appending the new item.
const [items, setItems] = useState<string[]>([]);
setItems((prev) => [...prev, 'New Item']);Remove Array ItemFilter out deleted element by matching identifier.
const remove = (id: string) =>
setList((prev) => prev.filter((it) => it.id !== id));State Architecture Best Practices
Avoid Redundant StateDerive computed values during render instead of storing state.
const [items, setItems] = useState<Item[]>([]);
// Computed dynamically on render
const total = items.reduce((s, i) => s + i.price, 0);Group Related FieldsCombine properties that always change together into an object.
const [position, setPosition] = useState({
x: 0,
y: 0,
});Reset State via KeyPass a different key prop to unmount and reset state cleanly.
<UserEditor key={userId} userId={userId} />Tips
- Pass a function into
useState(() => calc())to evaluate expensive initial state computations only once on mount. - Always create new object or array references when updating state with
...spreadoperators rather than modifying variables.
Warnings
- Never mutate state directly because
Reactstrictly relies on shallow reference inequality checks to trigger component re-renders. - Remember that state setter calls are
asynchronous, so reading state immediately after invoking its setter yields the old snapshot.
In Practice
Manage a dynamic shopping cart list with quantity increments and deletions.
- Declare cart state as an array of items.
- Increment item quantity using immutable map updates.
- Remove items cleanly using immutable filter operations.
- Derive total cart value during render without extra state.
interface CartItem { id: string; qty: number; }
export function CartView() {
const [cart, setCart] = useState<CartItem[]>([
{ id: 'item-1', qty: 1 },
]);
const update = (id: string, delta: number) => {
setCart((prev) =>
prev.map((it) =>
it.id === id
? { ...it, qty: Math.max(1, it.qty + delta) }
: it
)
);
};
return (
<CardView>
{cart.map((i) => (
<CartRow key={i.id} onUpdate={update} />
))}
</CardView>
);
}FAQ
State setters schedule a future re-render rather than mutating the variable in place. The updated state value becomes available on the subsequent render cycle of the component.
Use the functional updater form setVal(prev => prev + 1) whenever your new state depends on the prior state value or when updates occur inside asynchronous callbacks.
Use the object spread operator to copy all existing properties before overriding target keys: setUser(prev => ({ ...prev, name: 'Alice' })).