できない.dev

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 を渡すのが基本。
配列インデックスは「並び順が変わらない」前提でのみ許容する最終手段。

よくある原因

  1. map で key を渡していない: items.map(item => <li>{item}</li>) のように key を書き忘れている。
  2. index を key にしているが並び替える: 並び替え・追加・削除が起きるリストで key={i} を使うと、React は別物の要素同士を再利用してしまい、入力値や DOM 状態が他行に紛れる。
  3. 毎レンダリングで生成した ID: key={Math.random()} や render 内で crypto.randomUUID() を呼ぶと、毎回違う値になり再利用が一切効かない(実質 key 無しと同じ)。
  4. 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 の明示形に切り替える。

この記事は役立ちましたか?