React

Lesson 04

Updating state from previous state

Use functional state updates when the next value depends on the current value.

Good Code

ReviewerPicker.tsx
import { useState } from "react";

type Reviewer = {
  id: string;
  name: string;
};

export function ReviewerPicker({ reviewers }: { reviewers: Reviewer[] }) {
  const [selectedIds, setSelectedIds] = useState<string[]>([]);

  function toggleReviewer(id: string) {
    // Functional updates read the latest state React has queued.
    setSelectedIds((currentIds) =>
      currentIds.includes(id)
        ? currentIds.filter((currentId) => currentId !== id)
        : [...currentIds, id],
    );
  }

  return reviewers.map((reviewer) => (
    <button
      key={reviewer.id}
      type="button"
      aria-pressed={selectedIds.includes(reviewer.id)}
      onClick={() => toggleReviewer(reviewer.id)}
    >
      {reviewer.name}
    </button>
  ));
}

Bad Code

ReviewerPicker.tsx
import { useState } from "react";

type Reviewer = {
  id: string;
  name: string;
};

export function ReviewerPicker({ reviewers }: { reviewers: Reviewer[] }) {
  const [selectedIds, setSelectedIds] = useState<string[]>([]);

  function toggleReviewer(id: string) {
    // This uses the selectedIds captured by the current render.
    if (selectedIds.includes(id)) {
      setSelectedIds(selectedIds.filter((currentId) => currentId !== id));
      return;
    }

    setSelectedIds([...selectedIds, id]);
  }

  return reviewers.map((reviewer) => (
    <button
      key={reviewer.id}
      type="button"
      aria-pressed={selectedIds.includes(reviewer.id)}
      onClick={() => toggleReviewer(reviewer.id)}
    >
      {reviewer.name}
    </button>
  ));
}

Review Notes

What to review

Good Code

The good version asks React for the latest selected IDs before calculating the next state.

Bad Code

The bad version calculates from the value captured by the current render. That can lose updates when multiple events or batched changes happen close together.

Takeaways

  • When state depends on prior state, pass an updater function to the setter.