Updating objects in state

Copy a show object with spread when you change a field, including a nested rating.

A show in state is one object. Changing a field means handing React a new object. Copying with spread is the same rule as Destructuring, Spread, and Rest and Working with Objects.

Mutate and nothing moves

show.year = 2023 edits the object React already has. setShow(show) passes the same identity, so the card still says 2022.

import { useState } from "react";
import "./styles.css";
import ShowCard from "./ShowCard";

export default function App() {
  const [show, setShow] = useState({
    title: "The Bear",
    year: 2022,
    genre: "Drama",
    rating: { average: 8.6 },
    network: { name: "FX" },
    still: "still-warm",
  });

  function handleYear() {
    show.year = show.year + 1;
    setShow(show);
  }

  return (
    <div className="page">
      <ShowCard show={show} onYear={handleYear} />
    </div>
  );
}

Press Year + 1. The meta line still reads 2022. React compared by identity and saw the same object.

Copy the show

Spread the old show, then overwrite the field you want to change.

setShow({ ...show, year: show.year + 1 });
import { useState } from "react";
import "./styles.css";
import ShowCard from "./ShowCard";

export default function App() {
  const [show, setShow] = useState({
    title: "The Bear",
    year: 2022,
    genre: "Drama",
    rating: { average: 8.6 },
    network: { name: "FX" },
    still: "still-warm",
  });

  function handleYear() {
    setShow({ ...show, year: show.year + 1 });
  }

  function handleRename() {
    setShow({ ...show, title: "The Bear (FX)" });
  }

  return (
    <div className="page">
      <ShowCard show={show} onYear={handleYear} onRename={handleRename} />
    </div>
  );
}

Press Year + 1. The year becomes 2023. Press Rename. The heading becomes The Bear (FX).

Nested rating or network

Spread copies only the top level. rating is still the old object unless you copy that too.

setShow({
  ...show,
  rating: { ...show.rating, average: 8.7 },
});
import { useState } from "react";
import "./styles.css";
import ShowCard from "./ShowCard";

export default function App() {
  const [show, setShow] = useState({
    title: "The Bear",
    year: 2022,
    genre: "Drama",
    rating: { average: 8.6 },
    network: { name: "FX" },
    still: "still-warm",
  });

  function handleRaise() {
    setShow({
      ...show,
      rating: { ...show.rating, average: 8.7 },
    });
  }

  function handleMutate() {
    show.rating.average = 9;
    setShow(show);
  }

  return (
    <div className="page">
      <ShowCard show={show} onRaise={handleRaise} onMutate={handleMutate} />
    </div>
  );
}

Press Raise rating. The star becomes 8.7. Press Mutate rating. The number stays put, because the show object is still the same one.

Common mistakes

  • Mutating then setShow(show). React sees the same object and skips the commit.
  • Spreading only the top level. Nested rating and network need their own copies when you change a field inside them.
  • Editing a field and forgetting setShow. The data changes in memory. The card does not.

Exercise

Year + 1 mutates show.year and passes the same object back. The year never moves.

Fix it so one click changes the meta line from 2022 to 2023.

Hint: setShow({ ...show, year: show.year + 1 }).

import { useState } from "react";
import "./styles.css";
import ShowCard from "./ShowCard";

export default function App() {
  const [show, setShow] = useState({
    title: "The Bear",
    year: 2022,
    genre: "Drama",
    rating: { average: 8.6 },
    network: { name: "FX" },
    still: "still-warm",
  });

  function handleYear() {
    show.year = show.year + 1;
    setShow(show);
  }

  return (
    <div className="page">
      <ShowCard show={show} onYear={handleYear} />
    </div>
  );
}