Chapter 3 of 4
Objects and Arrays in State
Why you must replace state instead of mutating it, and the patterns for doing so.
React decides whether to re-render by comparing the new state with the old one by reference. Mutating an object keeps the same reference, so React sees no change and skips the update.
// Wrong: same object, React sees nothing new
function addTag(tag) {
tags.push(tag);
setTags(tags);
}
// Right: a new array
function addTag(tag) {
setTags([...tags, tag]);
}Array patterns
// Add to the end
setItems([...items, newItem]);
// Add to the start
setItems([newItem, ...items]);
// Remove by id
setItems(items.filter((item) => item.id !== id));
// Replace one item
setItems(items.map((item) =>
item.id === id ? { ...item, done: true } : item
));
// Sort without mutating the original
setItems([...items].sort((a, b) => a.name.localeCompare(b.name)));Object patterns
const [form, setForm] = useState({ name: "", email: "" });
// Update one field, keep the rest
function handleChange(field, value) {
setForm({ ...form, [field]: value });
}
// Nested objects need a copy at every level you change
setUser({
...user,
address: { ...user.address, city: "Berlin" },
});Keep state minimal
Do not store anything you can calculate from something else. Derived values in state go stale; derived values computed during render never do.
// Redundant: total can drift out of sync
const [items, setItems] = useState([]);
const [total, setTotal] = useState(0);
// Better: derive it
const [items, setItems] = useState([]);
const total = items.reduce((sum, item) => sum + item.price, 0);