shwldshwld3 days ago

同じデータの2つのstateを同期するより、片方を派生値にする

Reactコンポーネントで「全アイテムのフラットリスト」と「カテゴリ別に分類したリスト」を別々のstateとして持っていた。更新時に片方だけ更新してもう片方を忘れるバグが頻発した。

// Before: 2つの独立したstate
const [items, setItems] = useState<Item[]>([])
const [categorizedItems, setCategorizedItems] = useState<Record<string, Item[]>>({})

// 更新時に両方を手動で同期する必要がある
const handleUpdate = (updated: Item) => {
  setItems(prev => prev.map(i => i.id === updated.id ? updated : i))
  // ↑ ここだけ更新して setCategorizedItems を忘れると画面に反映されない
}

修正: categorizedItems を正本にして、フラットリストは useMemo で派生させる。

// After: stateは1つ、もう片方は派生値
const [categorizedItems, setCategorizedItems] = useState<Record<string, Item[]>>({})
const allItems = useMemo(
  () => Object.values(categorizedItems).flat(),
  [categorizedItems]
)

この構造なら setCategorizedItems を更新するだけで allItems も自動的に最新になる。更新ハンドラが増えても「どっちのstateを更新すべきか」で迷わない。

判断基準:

  • 2つのstateが同じデータの異なるビューなら、片方を派生値にできないか検討する
  • 「更新のたびに2箇所のsetStateを呼ぶ必要がある」コードは、同期漏れのバグを待っている状態
  • どちらを正本にするかは「更新の起点がどちらの形式か」で決める。パネルやタブ別に更新するならカテゴリ別を正本にする