React で「Each child in a list should have a unique key prop」警告が消せない
リスト描画の要素に一意な key を付けないと React は再利用判定に失敗し警告を出す。
配列インデックスは最終手段にして、データ由来の安定 ID を使うのが基本。
Fragment にも key を付ける場合は明示形を使う。
#react#key#list#rendering#warning
要約
Each child in a list should have a unique "key" prop の警告は、map などで生成した兄弟要素に 一意な key が付いていない ことを React が指摘している状態。key は再レンダリング前後で要素を同一視するための識別子で、データ由来の安定した ID を渡すのが基本。
配列インデックスは「並び順が変わらない」前提でのみ許容する最終手段。
よくある原因
- map で key を渡していない:
items.map(item => <li>{item}</li>)のように key を書き忘れている。 - index を key にしているが並び替える: 並び替え・追加・削除が起きるリストで
key={i}を使うと、React は別物の要素同士を再利用してしまい、入力値や DOM 状態が他行に紛れる。 - 毎レンダリングで生成した ID:
key={Math.random()}や render 内でcrypto.randomUUID()を呼ぶと、毎回違う値になり再利用が一切効かない(実質 key 無しと同じ)。 - Fragment shorthand に key を付けられない:
<>...</>構文には属性が書けない。keyが必要なら<Fragment key={id}>...</Fragment>を使う。
解決策
1. データ由来の id を使う
{users.map(u => (
<li key={u.id}>{u.name}</li>
))}id がサーバ側で割り当てられているなら最も安定する。key は同じ親内で一意であれば十分で、グローバルにユニークである必要はない(公式のリスト描画ドキュメント)。
2. index は静的リスト限定
並び替えや挿入が無いと確信できる場面のみ index を許容する。
{labels.map((label, i) => (
<Tag key={i}>{label}</Tag>
))}並び替えが入ったら別の安定 ID に切り替える。
3. id を生成するなら親で 1 度だけ
const [items, setItems] = useState(() =>
raw.map(r => ({ ...r, _id: crypto.randomUUID() }))
);useState のイニシャライザで一度だけ計算する。
子コンポーネントの render 中に randomUUID を呼ばない。
4. Fragment に key を付ける
import { Fragment } from "react";
{rows.map(r => (
<Fragment key={r.id}>
<dt>{r.label}</dt>
<dd>{r.value}</dd>
</Fragment>
))}公式の Fragment ドキュメント のとおり、<></> の shorthand には属性を書けないため、key が必要なときだけ Fragment の明示形に切り替える。