Props and state
Update objects and arrays without mutation
Create a new value for state so React receives a changed reference and previous render snapshots remain reliable.
Treat objects and arrays in state as immutable snapshots. Create the next value instead of changing the current one.
This mutation is a bug:
tasks[0].done = true
setTasks(tasks)
The array reference has not changed, and an object used by an earlier render was modified in place. React may skip a re-render because tasks === tasks. Even if it re-renders, other components still holding the old snapshot now see different data than they expected.
Use non-mutating array methods and object spread:
setTasks(currentTasks =>
currentTasks.map(task =>
task.id === id
? { ...task, done: true }
: task
)
)
The new array contains a new object for the changed task. Unchanged task objects keep their existing references.
Spread is shallow. For nested data, copy every changed level:
setUser(user => ({
...user,
address: {
...user.address,
city: 'Copenhagen'
}
}))
Keep state shapes simple. Deeply nested state makes updates harder and increases the chance of accidental mutation.
For arrays, use map() to replace, filter() to remove, and spread to add. Avoid mutating methods such as push(), pop(), and splice() on state arrays.
When you need to append, spread the old array and add the new item:
setTasks(current => [...current, newTask])
That gives React a new reference and keeps every previous snapshot intact.
Immer and similar libraries help when nested updates get tedious. They still produce new references under the hood. React only cares that the reference changed, not how you built the copy.
If you forget and mutate state, React DevTools can still show the new value while an older sibling component renders stale data from its snapshot. That mismatch is the bug immutability prevents.
Log the old and new references with ===. The array and changed object should differ. Unchanged objects can remain equal.
Lesson completed