State: useState and useReducer
Introduction
State is data that changes over time and triggers re-renders. useState handles most component state; useReducer suits complex updates. This chapter covers immutable updates, functional setters, derived values, and when not to duplicate state.
Prerequisites
useState Basics
Code explanation:
useState(initial)returns[value, setValue]- Calling
setCountschedules a re-render with the new value - Functional updates
setCount(c => c + 1)are safe when the next state depends on the previous—especially in rapid clicks
Compare ref in Vue reactivity—React re-renders the whole function component when state changes.
Immutable Object Updates
Do not mutate state in place:
// Wrong
user.name = "Ada";
setUser(user);
// Right
setUser({ ...user, name: "Ada" });Array updates:
// Add item
setItems((prev) => [...prev, newItem]);
// Remove by id
setItems((prev) => prev.filter((item) => item.id !== id));
// Toggle field
setItems((prev) =>
prev.map((item) =>
item.id === id ? { ...item, done: !item.done } : item
)
);React compares state by reference—mutating the same object may skip re-renders.
Multiple useState Calls
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);Split unrelated state—easier to read than one giant object unless fields always change together.
Derived State
Do not store values you can compute during render:
const [todos, setTodos] = useState<Todo[]>([]);
// Derived — no extra useState
const activeCount = todos.filter((t) => !t.done).length;
const doneCount = todos.length - activeCount;For expensive derivations, use useMemo in Performance—like Vue computed.
useReducer (Introduction)
When the next state depends on a action label and logic grows:
This track uses Zustand for shared app state—useReducer is enough for local complex forms.
State Batching
React 18 batches multiple setState calls in the same event into one re-render:
function handleClick() {
setA(1);
setB(2);
// One re-render after both updates
}Lifting State Up (Preview)
When siblings need the same data, move state to their common parent and pass props down—Composition, Props, and Lifting State.
FAQ
State not updating?
You may be mutating objects/arrays. Return new references with spread/map/filter.
useState with objects?
const [form, setForm] = useState({ email: "", password: "" });
setForm((f) => ({ ...f, email: "a@b.com" }));Initial state from props?
const [value, setValue] = useState(initialProp);If initialProp changes later, local state does not auto-sync—use key on the component to reset or sync in useEffect.
useState lazy init?
const [data, setData] = useState(() => expensiveParse(raw));Function runs once on mount.
useReducer vs Zustand?
useReducer for one component or small tree; Zustand for app-wide shared state—chapter 15.