BLOG

ブログ

useStateで配列を更新する時のベストプラクティス

Reactで配列を扱うとき、次のように配列を触ったことはありませんか?

items.push(newItem);
setItems(items);

実はこれアンチパターンです。なぜかも含めて解説します。

失敗例(アンチパターン)

import { useState } from 'react';

export const Component = () => {
  const [items, setItems] = useState<string[]>([]);

  const addItem = () => {
    items.push("new item");
    setItems(items);
  };

  return (
    <>
      <button onClick={addItem}>Add Item</button>
      <ul>
        {items.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </>
  );
};

原因はここ

  const addItem = () => {
    items.push("new item"); // ← 配列を直接変更している
    setItems(items);        // ← 参照が変わらないので再レンダリングされないことがある
  };

stateである配列をsetter関数を使わずに直接変更すると、Reactはstateが変わったと認識できず、再レンダリングされません。

解説

  • Reactのstateは不変性 (immutability) を守る必要があります。
    • Reactは「stateの値が変わったかどうか」を参照(メモリ上の場所)が変わったかで判断しており、stateの中身だけを変えても「変わっていない」と思われてしまう。
    • 新しい箱を作ってそこに中身を入れるイメージで扱う必要がある。
  • pushspliceは既存配列を変更してしまうため、破壊的な変更はしないのがベター
  • 結果的に「新しい参照」が作られないため、Reactが変更を検知できない

正しい書き方

spread構文concat を使って新しい配列を作るのがより良いです。

もしくは関数型アップデートを使いましょう

const addItem = () => {
  setItems([...items, "new item"]);
};

// もしくは

const addItem = () => {
  setItems(prev => [...prev, "new item"]);
};

→ 非同期でstateが更新される場合でも安全に追加できます

削除や更新の例

追加だけでなく、削除・更新でも「新しい配列を返す」形にすることが大事です。

削除

const removeItem = (index: number) => {
  setItems(prev => prev.filter((_, i) => i !== index));
};

更新

const updateItem = (index: number, newValue: string) => {
  setItems(prev => prev.map((item, i) => i === index ? newValue : item));
};

まとめ

  • useStateで配列を扱うときは 不変性を守ること
  • push / splice はstateを扱うには基本NG、代わりに spreadfilter / map を使う
  • 関数型アップデート を使うと非同期更新にも強い

RELATED ARTICLE