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の中身だけを変えても「変わっていない」と思われてしまう。
- 新しい箱を作ってそこに中身を入れるイメージで扱う必要がある。
pushやspliceは既存配列を変更してしまうため、破壊的な変更はしないのがベター
- 結果的に「新しい参照」が作られないため、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、代わりにspreadやfilter/mapを使う- 関数型アップデート を使うと非同期更新にも強い






















