Updating arrays in state

Toggle watched on a list of shows by mapping to a new array, not by mutating.

The watchlist is now an array in state. Change one card with Array Methods map, and give each row a key from Rendering a list of shows.

Mutate the array and the badges stay put

shows[0].watched = true edits the array React already has. setShows(shows) passes the same identity, so The Bear's pill never appears.

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

const start = [
  { id: "s1", title: "The Bear", year: 2022, genre: "Drama", rating: 8.6, still: "still-warm", watched: false },
  { id: "s2", title: "Severance", year: 2022, genre: "Thriller", rating: 8.7, still: "still-cold", watched: false },
  { id: "s3", title: "Andor", year: 2022, genre: "Sci-Fi", rating: 8.4, still: "still-dusty", watched: true },
];

export default function App() {
  const [shows, setShows] = useState(start);

  function handleToggle(id) {
    const match = shows.find((show) => show.id === id);
    match.watched = !match.watched;
    setShows(shows);
  }

  return (
    <div className="page">
      {shows.map((show) => (
        <ShowCard key={show.id} show={show} onToggle={handleToggle} />
      ))}
    </div>
  );
}

Andor starts with a pill. Tick The Bear. That still stays empty.

map to a copy

map returns a new array. Copy the item you change as well, with the same spread as the last lesson.

setShows(
  shows.map((show) =>
    show.id === id ? { ...show, watched: !show.watched } : show
  )
);
import { useState } from "react";
import "./styles.css";
import ShowCard from "./ShowCard";

const start = [
  { id: "s1", title: "The Bear", year: 2022, genre: "Drama", rating: 8.6, still: "still-warm", watched: false },
  { id: "s2", title: "Severance", year: 2022, genre: "Thriller", rating: 8.7, still: "still-cold", watched: false },
  { id: "s3", title: "Andor", year: 2022, genre: "Sci-Fi", rating: 8.4, still: "still-dusty", watched: true },
];

export default function App() {
  const [shows, setShows] = useState(start);

  function handleToggle(id) {
    setShows(
      shows.map((show) =>
        show.id === id ? { ...show, watched: !show.watched } : show
      )
    );
  }

  return (
    <div className="page">
      {shows.map((show) => (
        <ShowCard key={show.id} show={show} onToggle={handleToggle} />
      ))}
    </div>
  );
}

Tick The Bear. Its pill appears. Andor stays watched.

Adding or removing

[...shows, newShow] adds a card. filter drops one. Do not sort here.

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

const start = [
  { id: "s1", title: "The Bear", year: 2022, genre: "Drama", rating: 8.6, still: "still-warm", watched: false },
  { id: "s2", title: "Severance", year: 2022, genre: "Thriller", rating: 8.7, still: "still-cold", watched: false },
  { id: "s3", title: "Andor", year: 2022, genre: "Sci-Fi", rating: 8.4, still: "still-dusty", watched: true },
];

export default function App() {
  const [shows, setShows] = useState(start);

  function handleDropAndor() {
    setShows(shows.filter((show) => show.id !== "s3"));
  }

  return (
    <div className="page">
      <button onClick={handleDropAndor} className="drop-button">
        Drop Andor
      </button>
      <div className="row">
        {shows.map((show) => (
          <ShowCard key={show.id} show={show} />
        ))}
      </div>
    </div>
  );
}

Press Drop Andor. Two cards remain. filter handed React a new array.

Common mistakes

  • shows.push. That mutates the old array. Spread or map instead.
  • Flipping show.watched then returning that same object from map. Copy the item: { ...show, watched: !show.watched }.
  • Forgetting the key. Use a stable id, as Rendering a list of shows covers.

Exercise

The checkboxes mutate each show in place, so ticks do nothing on screen. Andor already starts watched.

Fix the toggle. Tick The Bear and Severance: both stills show a Watched pill. Andor keeps its pill from the first paint.

Hint: map to a new array, and spread the item you change.

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

const start = [
  { id: "s1", title: "The Bear", year: 2022, genre: "Drama", rating: 8.6, still: "still-warm", watched: false },
  { id: "s2", title: "Severance", year: 2022, genre: "Thriller", rating: 8.7, still: "still-cold", watched: false },
  { id: "s3", title: "Andor", year: 2022, genre: "Sci-Fi", rating: 8.4, still: "still-dusty", watched: true },
];

export default function App() {
  const [shows, setShows] = useState(start);

  function handleToggle(id) {
    const match = shows.find((show) => show.id === id);
    match.watched = !match.watched;
    setShows(shows);
  }

  return (
    <div className="page">
      {shows.map((show) => (
        <ShowCard key={show.id} show={show} onToggle={handleToggle} />
      ))}
    </div>
  );
}