State with Objects and Arrays
In this page:
Updating an Object in State
To update one property of an object in state, spread the existing object into a new one and override just the property that changed. This creates a brand-new object reference, which is what lets React detect the update.
Note: The pattern setState({...state, propertyToChange: newValue}) covers the vast majority of object state updates.
Warning: Writing state.name = New; setState(state); mutates the old object and often fails to trigger a re-render.
Example: Updating an Object in State
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function ProfileForm() {
const [user, setUser] = React.useState({ name: "Sam", age: 25 });
return (
<div>
<p>{user.name}, {user.age}</p>
<button onClick={() => setUser({...user, age: user.age + 1})}>Birthday!</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<ProfileForm />);
</script>
</body>
</html>
Adding to an Array in State
To add an item to an array in state, create a new array using the spread operator plus the new item, rather than calling .push() on the existing array. This keeps the update immutable and ensures React notices the change.
Note: setItems([...items, newItem]) is the standard pattern for adding to array state.
Warning: items.push(newItem); setItems(items); mutates the original array and can cause React to miss the update.
Example: Adding to an Array in State
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function TodoList() {
const [items, setItems] = React.useState(["Buy milk"]);
function addItem() {
setItems([...items, "New task " + (items.length + 1)]);
}
return (
<div>
<ul>{items.map((item, i) => <li key={i}>{item}</li>)}</ul>
<button onClick={addItem}>Add Item</button>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<TodoList />);
</script>
</body>
</html>
Removing from an Array in State
To remove an item, create a new array that filters out the unwanted item using .filter(), rather than using a mutating method like .splice(). .filter() naturally returns a new array containing only the items that pass the check.
Note: Filtering by a unique ID (rather than array index) is safer when items can be reordered or added.
Warning: items.splice(index, 1) mutates the array in place and is easy to misuse with React state.
Example: Removing from an Array in State
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
function TodoList() {
const [items, setItems] = React.useState(["Buy milk", "Walk dog", "Read book"]);
function removeItem(index) {
setItems(items.filter((_, i) => i !== index));
}
return (
<ul>
{items.map((item, i) => <li key={i}>{item} <button onClick={() => removeItem(i)}>x</button></li>)}
</ul>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<TodoList />);
</script>
</body>
</html>
- Calling
.push(),.splice(), or similar mutating array methods directly on state. - Modifying a nested property of a state object directly (
state.user.name = New) instead of spreading it into a new object. - Forgetting that spreading only copies one level deep -- nested objects/arrays still need their own spread.
- Objects and arrays in state must be replaced with new copies, not mutated.
- The spread operator (
...) is the standard tool for copying and updating objects/arrays immutably. - Array methods like
.map(),.filter(), and.concat()return new arrays instead of mutating. - Nested state requires spreading at every level that changes.
No browser-specific restrictions -- relies on standard JavaScript spread syntax (ES2018+).
Chapter Quiz — Complete all 10 topics to unlock
0/10 topics done
Complete these topics first: