How to Update Objects in React State

Update objects in React state by replacing them with new copies instead of mutating. Use the spread syntax for flat fields and nested updates.

5 min read

To update objects in React state, replace the object with a new copy instead of changing the existing one. Mutating an object does not trigger a render and corrupts the previous snapshot. The spread syntax is the usual way to build that copy, and it works the same for nested objects with one extra step.

Replace instead of mutate

Objects in state are meant to be treated as read-only. Changing a property directly mutates data from a previous render, and React never learns that a new value arrived. The safe habit is to always hand the setter a brand new object.

App.jsxApp.jsx
import { useState } from "react";
 
function Player() {
  const [player, setPlayer] = useState({ name: "Ada", score: 0 });
  function handleScore() {
    player.score = player.score + 1;
  }
  return <button onClick={handleScore}>{player.name}: {player.score}</button>;
}

Clicking the button changes nothing on screen. The score property was mutated, but the object is the same one from before, so React sees no change and skips the render.

React compares the old and new values with Object.is. Mutating in place leaves the reference unchanged, so the comparison reports no difference even though a property moved.

Treating state as immutable also keeps debugging clear, because an old log still shows the old object. It makes future features like undo and redo far easier to add, since past versions are never overwritten.

Copy with the spread syntax

Build a new object that copies the old fields and overrides the one you changed. The spread lists the old fields first, then the override wins, so pass the result to the setter returned by useState:

App.jsxApp.jsx
function handleScore() {
  setPlayer({ ...player, score: player.score + 1 });
}

Now the score climbs with each click. React receives a fresh object, compares it with the old one, sees they differ, and renders the updated score.

The spread copies every existing field first, then the new score wins. The same pattern works for any field, not just numbers.

Updating one field in a form

A form keeps all its fields in one object. Each change copies the object and overrides a single field, so a form with three fields stays in one variable instead of three.

App.jsxApp.jsx
function Form() {
  const [person, setPerson] = useState({ first: "Ada", last: "Lovelace" });
  function handleChange(event) {
    setPerson({ ...person, first: event.target.value });
  }
  return <label>First name: <input value={person.first} onChange={handleChange} /></label>;
}

Type in the input and the text updates as you go. Every keystroke replaces the person object with a new copy that keeps last unchanged and updates first.

For several fields, one handler can use the input name as the key, writing the setter with a computed property instead of a separate function per field. The same single handler then serves first, last, and email by reading the name attribute of the input that fired.

Updating a nested object

A nested field needs copies at every level above it. If person holds a nested profile, update it by copying both the outer and inner objects.

App.jsxApp.jsx
setPerson({
  ...person,
  profile: { ...person.profile, city: "London" },
});

The outer spread keeps name and the other fields, and the inner spread keeps the profile fields while changing only city. For anything deeper, flatten the state or use a library like Immer rather than stacking spreads.

The same replace-not-mutate rule applies to arrays, which are objects under the hood. Once you think in copies, both shapes use the same muscle memory.

Common mistakes

  • Forgetting the spread deletes every other field when you replace the object.
  • Mutating first and then calling the setter with the same object skips the render.
  • Copying only the outer object still shares nested objects that other code may reference.
  • Overwriting the object without the spread silently drops fields the next render still needs.

What to learn next

Group related fields in one object when they change together, such as the fields of one form. When several values are independent, separate state variables are often cleaner and simpler to update. Matching the shape of the state to how it updates removes most of the copying work, and flatter state is easier to copy.

Rune AI

Rune AI

Key Insights

  • Never mutate an object that is already in state.
  • Replace it with a new object passed to the setter.
  • Spread copies existing fields, then override the changed one.
  • Nested fields need copies at every level above them.
  • Forgetting the spread deletes every other field.
RunePowered by Rune AI

Frequently Asked Questions

Why does mutating an object in state do nothing?

The object is the same reference as before, so React sees no change and skips the render. You must pass a new object to the setter.

What does the spread syntax copy?

It copies properties one level deep. Nested objects are still shared, so a nested field needs a copy at every level above it.

Is mutation ever allowed?

Only on an object you just created and that nothing else references yet. Objects already in state should always be replaced.

Conclusion

Treat objects in state as read-only and replace them with new copies. The spread syntax copies the old fields, and a nested field needs a copy at every level above it.