【解決】Each child in a list should have a unique key エラーの修正方法
ReactのEach child in a list should have a unique 'key' propエラーを解説。keyが必要な理由・正しいkeyの選び方・indexをkeyにしてはいけないケースを実例付きで説明します。
制作・運営:React Guide 運営者
コンソールに出るこの警告、最初は「とりあえず動いてるからいいか」と放置しがちだ。
Warning: Each child in a list should have a unique "key" prop.でもこれを放置すると、リストの並び替えや更新のとき画面がおかしな挙動をする。エラーではなく「Warning」なのに対処すべき理由がそこにある。
なぜReactはkeyを必要とするのか
Reactは画面を更新するとき、前回の状態と今回の状態を比較して「何が変わったか」を計算する。このとき、リストの各要素を識別する手がかりが必要になる。
keyがない場合、Reactは「順番」で判断するしかない。1番目・2番目・3番目という位置だけを頼りに差分を計算する。
これが問題になるのは、リストの順番が変わるときだ。
変更前: [A, B, C]
変更後: [B, C] ← Aを削除したkeyがないとReactはこう解釈する。「1番目がAからBに変わり、2番目がBからCに変わり、3番目が消えた」。実際には「1番目が消えただけ」なのに、3つの変更として処理してしまう。
keyがあればこうなる。「key=Bの要素とkey=Cの要素はそのまま。key=Aの要素が消えた」。残す要素を識別できる。ここでの説明は要素の対応付けであり、実際のDOM操作回数を保証するものではない。
コンポーネントに状態(入力値など)がある場合、keyなしのリストでは「位置」で状態が紐づくため、要素を削除や並び替えしたときに状態が意図せず別の要素に引き継がれることがある。
基本の修正:ユニークなIDをkeyに使う
// ❌ keyがない
function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map((user) => (
<li>{user.name}</li>
))}
</ul>
)
}
// ✅ データが持つユニークなIDをkeyに使う
function UserList({ users }: { users: User[] }) {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}keyに使うべき値の基準は「そのデータを一意に識別できる、変わらない値」だ。データベースのIDやユーザーIDなど、バックエンドが発行したIDが最適。
フラグメントにkeyを付けたいとき
複数の要素をまとめてレンダリングするとき、短縮記法の <>...</> ではkeyが付けられない。
// ❌ 短縮記法はkeyを付けられない
{items.map((item) => (
<>
<dt>{item.term}</dt>
<dd>{item.desc}</dd>
</>
))}
// ✅ Fragment の明示的な記法を使う
import { Fragment } from 'react'
{items.map((item) => (
<Fragment key={item.term}>
<dt>{item.term}</dt>
<dd>{item.desc}</dd>
</Fragment>
))}indexをkeyにしていいケース・だめなケース
「IDがないからindexを使えばいい」と思いがちだが、これは条件次第だ。
indexを使ってはいけないケース:リストに変更がある場合
// ❌ 並び替えや削除がある場合、indexをkeyにすると状態がずれる
{todos.map((todo, index) => (
<TodoItem key={index} todo={todo} />
))}たとえばindex=0のTodoItemがチェックされている状態で、0番目のアイテムを削除すると、元1番目だったアイテムがindex=0になる。Reactはindex=0のコンポーネントが「同じもの」と判断して状態を引き継いでしまう。結果、チェック状態が別のアイテムに移る。
// ✅ IDがある場合は必ずIDを使う
{todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} />
))}indexを使ってよいケース:リストが変化しない場合
// ✅ ナビゲーションリンクなど、順番も内容も変わらない静的なリスト
const navLinks = ['ホーム', '記事一覧', 'について']
{navLinks.map((link, index) => (
<li key={index}>{link}</li>
))}変わらない・並び替えない・状態を持たない——この3条件が揃えばindexでも問題ない。
よくある間違ったkeyの付け方
// ❌ Math.random()は毎回変わる → レンダリングのたびに全要素が再生成される
<Item key={Math.random()} />
// ❌ 配列のindexと値を組み合わせても、根本の問題は変わらない
<Item key={`item-${index}`} />
// ✅ データが持つ安定したIDを使う
<Item key={item.id} />
// ✅ 単一フィールドでユニークにならない場合は複数を組み合わせる
<Item key={`${item.userId}-${item.date}`} />keyはpropsとして受け取れない
key はReact内部の管理用であり、コンポーネントのpropsとしては渡ってこない。
// ❌ props.key は常にundefined
function Item({ key, name }: { key: string; name: string }) {
return <li>{key}: {name}</li>
}
// ✅ 別名のpropsとして渡す
function Item({ id, name }: { id: string; name: string }) {
return <li>{id}: {name}</li>
}
{items.map((item) => (
<Item key={item.id} id={item.id} name={item.name} />
))}このエラーと向き合うための1つの考え方
リストの各要素には、データ側が持つ「本人確認できる番号」をkeyに使う。
位置(index)ではなく、そのデータ自体のアイデンティティをkeyに渡すことで、Reactはリストがどう変化しても正確に差分を計算できる。
入力値が移る現象を試す
復習メモの並べ替え実験では、行ごとにstateを持つ2つのリストを操作できます。左右のPropsに同じメモを入力して順序を反転し、indexを使った列とIDを使った列でメモの行を比べてください。
keyは兄弟要素の間で一意であればよく、サイト全体で一意である必要はありません。ローカルで項目を作る場合は、作成時にIDを生成してデータに保存します。レンダーのたびにIDを作り直すと別要素として扱われます。
仕様の参照先
確認してみる
複合keyも重複しないことが前提。同じuserId・dateの行を複数追加する要件なら、別の行IDを用意する。
同じテーマの教材
リストレンダリングとkey:配列データからUIを生成する
Reactでの配列のmap()によるリストレンダリングを解説。keyプロパティが必要な理由と、よくある間違いをFeatureCard実装を通じて学ぶ。
4択クイズを作ろう(正解と点数を出そう)
選択肢を押すと正解・不正解がわかり点数がたまる4択クイズを作る回。配列とmapで選択肢を並べ、stateで点数を管理する方法を小学生向けにやさしく解説します。
propsとmap:自己紹介カードをたくさん並べよう
外から情報を渡すpropsと、配列から要素を並べるmapを小学生向けに解説。名まえカードを使い回して、自己紹介カードやミニ図鑑をたくさん並べる方法を作りながら学びます。