👻

【React】keyにindexを指定してはいけない理由

2025/01/11に公開

keyにindexを指定するとLinterに怒られる

Linterにbiomeを使っているのですが、mapなどのループ処理で要素を作る時、後でちゃんとなおすからと、ついつい手っ取り早くkeyindexを指定してしまうことがあります。
今回、なぜそれがダメなのかちゃんと勉強してみたので、記録します。

Avoid using the index of an array as key property in an element.biomelint/suspicious/noArrayIndexKey

何が起こるか

下記のcodesandboxはkeyにindexを指定した場合に起こる不都合を再現しています。
biomeの公式Docsでこの問題のブログ記事「indexをkeyに使うのはアンチパターン」が紹介されてますが、classコンポーネントで書かれていたので、勉強ついでに自分なりに簡易版としてリメイクしてみました。

下記のcodesandboxのコード右にあるバーをドラッグすると、プレビューが見れます。

sample code

簡略化したコード

オブジェクトが複数格納された配列arrをmapして、<label><input>を生成します。
コードは省きますが、arrに要素を追加するとarrの先頭に要素が追加されます。

export const Component = () => {
  const arr = [
    { name: “Bar” }
    { name: “Foo” },
  ];

  return (
    {arr.map((item, index) => {
       return(
         <div key={index}>
           <label>item.name</label>
           <input value=””>
         </div> 
       );
    )}
  );
}

indexをkeyにした場合【NG】

Reactはkeyを手がかりにDOMを管理していて、変更された箇所だけを再描画します。
なので、配列に要素を追加すると下記のようになります。

追加前

React「key=0inputvalueは”sample-text”だね!OK!」

[
  { name: "Bar" }, // keyがindex=0 => <input value="sample-text" />
  { name: "Foo" }, // keyがindex=1 => <input value="" />
]

追加後

React「key=0inputvalueは”sample-text”のハズだから、そのままでヨシ!」× NG

[
  { name: "Baz" }, // keyがindex=0 => <input value="sample-text" />  
  { name: "Bar" }, // keyがindex=1 => <input value="" />
  { name: "Foo" }, // keyがindex=2 => <input value="" />
]

index以外のユニークな値をkeyにした場合【OK】

ユニークな値をkeyにしておけば、Reactは間違わない。

追加前

React「key=binputvalueは”sample-text”だね!OK!」

[
  { name: "Bar", id=”b” }, // key=b => <input value="sample-text" />
  { name: "Foo", id=”a” }, // key=a => <input value="" />
]

追加後

React「key=binputvalueは”sample-text”のハズだから、そのままでヨシ!」○ OK

[
  { name: "Baz", id=”c” }, // key=c => <input value="" />  
  { name: "Bar", id=”b” }, // key=b => <input value="sample-text" />
  { name: "Foo", id=”a” }, // key=a => <input value="" />
]

それでもindexをkeyにしたい場合

入れ替えが絶対に発生しないことが確約されていて、他にkeyに使えるものがない場合は、Linterのignoreやコメントでその旨書いておくと良さそうです。

  <form className="flex flex-col gap-3 form-group">
    {todoList.map((todo, index) => {
      // biome-ignore lint: 入れ替えは発生しないのでindexをkeyに設定.
      return <Item todo={todo} key={index} />;
    })}
  </form>

Discussion