React State with Objects
Table of Contents + −
In React Updating State you changed one value at a time. This lesson holds many related values in a single object, with one rule you must follow or the screen won’t update.
🤔 Why hold an object in state?
Picture a user profile form. It has a few fields that belong together.
- You could make one
useStatefor the name, one for the age, one for the email, and so on. - That works, but it gets messy fast. Lots of separate state variables for one thing.
- It’s cleaner to keep all the related fields in a single object. One state variable holds the whole profile.
Here is how you put an object into state with useState. You pass the starting object as the initial value.
import { useState } from "react";
function Profile() { const [user, setUser] = useState({ name: "Alex", age: 25 });
return ( <div> <h2>{user.name}</h2> <p>Age: {user.age}</p> </div> );}So now user holds the whole object, and you read each field with user.name and user.age. One state variable, many values inside it.
⚠️ Never change the object directly
Here is the rule that trips up almost everyone. You must never change the object directly. This is called mutation, and React ignores it.
- React only re-renders when you call the setter function, like
setUser. - It also checks if the object is a new object before re-rendering. If you change the old one in place, it looks the same to React, so nothing happens on screen.
- Changing a field with
user.age = 26edits the existing object. React sees the same object and does not update.
This example shows the wrong way and the right way side by side.
// ❌ Wrong - changing the object in place, then passing the same objectfunction birthday() { user.age = 26; // mutating the existing object setUser(user); // same object, React sees no change, screen stays the same}
// ✅ Right - build a brand new object and pass thatfunction birthday() { setUser({ ...user, age: 26 }); // a new object, React re-renders}So the fix is to always hand React a fresh object, not the old one with a small edit.
Mutation = no re-render
If you change a state object directly and call the setter with that same object, React compares it to the old one, sees the same object, and skips the re-render. Your data may have changed in memory, but the screen will not.
✨ Update one field with the spread operator
So how do you make a new object that keeps every field except the one you’re changing? You use the spread operator, which is the three dots ....
...usercopies every key and value from the old object into the new one.- After the spread, you list the field you want to override, like
age: 26. - React gets a brand new object, so it re-renders. The rest of the fields stay exactly as they were.
This shows how to update just the age while keeping the name untouched.
const [user, setUser] = useState({ name: "Alex", age: 25 });
// Copy everything from user, then overwrite age with 26setUser({ ...user, age: 26 });// Result: { name: "Alex", age: 26 }So name is copied over by the spread, and age gets the new value. You only typed the one field that changed, and the rest came along for free.
Order matters
Put the spread first, then your changes: { ...user, age: 26 }. The later key
wins, so age: 26 overrides the age that came in from ...user. If you put
the spread last, it would overwrite your new value with the old one.
📝 A form field that updates state
Let’s tie it together with a real form. The user types in an input, and we update just the name field in state while keeping age as it is.
import { useState } from "react";
function ProfileForm() { const [user, setUser] = useState({ name: "Alex", age: 25 });
function handleNameChange(event) { // keep age the same, only replace name with what was typed setUser({ ...user, name: event.target.value }); }
return ( <div> <input value={user.name} onChange={handleNameChange} /> <p>Name: {user.name}</p> <p>Age: {user.age}</p> </div> );}Here’s what happens step by step when someone types.
- Every keystroke fires
handleNameChangewith the typed text inevent.target.value. - We call
setUserwith a new object:...userkeeps the oldage, andnamegets the new text. - React re-renders, the input shows the new name, and
agestays at 25 the whole time.
So the one field you touch changes, and everything else in the object is safe.
🪜 Nested objects need spread at each level
What if your object has another object inside it? The spread operator only copies the top level, so you have to spread each level you go into.
- A shallow spread like
...usercopies the inner object by reference, not a fresh copy. - To safely update a nested field, spread the outer object and spread the inner one too.
- Each level you change needs its own
....
This updates a city inside a nested address object without losing the other fields.
const [user, setUser] = useState({ name: "Alex", address: { city: "Berlin", zip: "10115" },});
// Spread the outer object, then spread address, then change citysetUser({ ...user, address: { ...user.address, city: "Munich" },});// Result keeps name and zip, only city changesSo you copy user, then rebuild address from its old values plus the new city. The name and zip both stay safe.
Deeply nested gets verbose
Spreading at every level works, but it gets long when objects are nested deep. For big nested state, many teams reach for a tool like a reducer or a small helper library. For now, spread-per-level is the core idea to understand.
🧩 What You’ve Learned
- ✅ You can hold many related values in one object in state with
useState - ✅ Read each field with dot notation, like
user.nameanduser.age - ✅ Never change a state object directly, because React skips the re-render
- ✅ Build a brand new object instead, so React sees a change and re-renders
- ✅ The spread operator
...usercopies every field into a new object - ✅ List the changed field after the spread to override just that one field
- ✅ Nested objects need a spread at each level you want to update
Check Your Knowledge
Test what you learned. Pick an answer for each question, then click Check.
- 1
Why does `user.age = 26; setUser(user);` fail to update the screen?
Why: Editing the existing object and passing it back gives React the same object reference. React compares it to the old one, sees no change, and does not re-render.
- 2
What is the correct way to set age to 26 while keeping the other fields?
Why: Spread copies every existing field into a new object, then age: 26 overrides just the age. React gets a fresh object and re-renders.
- 3
In `{ ...user, age: 26 }`, why must the spread come before age?
Why: When the same key appears twice, the later one wins. With the spread first, your age: 26 overrides the old age. If the spread came last, it would overwrite your new value.
- 4
How do you update a field inside a nested object in state?
Why: A single spread only copies the top level. To safely update a nested field you spread the outer object and spread the inner object too, changing the field at that level.
🚀 What’s Next?
Now you can hold and update an object in state without breaking re-renders. Next you’ll do the same thing with lists, where the spread operator and the no-mutation rule show up again in a slightly different shape.