Локальное и общее состояние — Примеры

Пример 1 — Два компонента, синхронизирующие один факт

Поле поиска и счётчик результатов держат по собственной копии запроса и синхронизируют их эффектом. Стоит быстро нажать дважды, и значения у них разойдутся. Скажите, что скрывает эффект, и дайте значению одного владельца.

function SearchField() {
  const [query, setQuery] = useState("");
  return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}

function ResultCount({ results }) {
  const [query, setQuery] = useState("");
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(results.filter((r) => r.name.includes(query)).length);
  }, [results, query]);
  return <span>{count} shown</span>;
}

Решение

function Page({ results }) {
  const [query, setQuery] = useState("");
  const count = results.filter((r) => r.name.includes(query)).length;

  return (
    <>
      <SearchField value={query} onChange={setQuery} />
      <ResultCount count={count} />
    </>
  );
}

Эффект, который поддерживает синхронность двух копий одного факта, выполняется после рендера, а не во время него, поэтому взаимодействие, меняющее оба значения в одном такте, оставляет копии в разногласии — вот почему баг выглядит как проблема отрисовки. Подъём означает перенос владельца, а не добавление синхронизатора: значение уходит к ближайшему общему предку, тот передаёт вниз значение и наверх сеттер, а локальные копии удаляются. Счётчик после этого не хранится, а выводится во время рендера, поэтому устареть он не может вовсе.

Пример 2 — Запись хранилища, которую читает только один экран

Выбранную строку одной таблицы хранилище держит вместе с эффектом, который сбрасывает её при размонтировании экрана. Больше это значение не читает никто. Скажите, чего стоит хранилище, и верните значение туда, где его используют.

// store.js
export const useAppStore = create((set) => ({
  selectedRow: null,
  setSelectedRow: (id) => set({ selectedRow: id }),
  resetSelection: () => set({ selectedRow: null }),
}));

// OrdersTable.jsx
useEffect(() => () => resetSelection(), []);   // очистка для локального значения

Решение

function OrdersTable() {
  const [selectedRow, setSelectedRow] = useState(null);
  // сбрасывать нечего: размонтирование забирает значение с собой
  return <table>{/* строки вызывают setSelectedRow(row.id) */}</table>;
}

Экран живёт меньше, чем глобальное хранилище, поэтому значение, принадлежащее одному экрану, приходится чистить вручную. Эта очистка и есть признак того, что общим значение никогда не было. С локальным хранением сбрасывать нечего, сериализовать нечего, и нет записи, которую следующий экран может случайно прочитать: время жизни значения совпадает со временем жизни компонента. Направление движения — от локального наружу: поднимайте значение только тогда, когда появился второй, далёкий читатель или когда оно должно пережить навигацию. Хранилище заслуживает своё место благодаря сквозной задаче, а не важности значения.