ノブ「JSX内のmapメソッドでindexをkeyに指定しないでくれぇ」

## とある開発会社での千鳥の会話

ノブ「大吾、手隙か？」
大吾「手隙じゃ」
ノブ「ちょいと大吾に頼みたいことがある」
大吾「なんでもまかせろ」
ノブ「ユーザーが自由に入力フォームを追加できる機能を作ってくれんか」
ノブ「完成形はこんな感じじゃ。増やすボタンを押すと入力フォームが追加されるイメージじゃ」

![スクリーンショット 2022-07-26 8.39.09.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/92ef01b2-e128-f03a-3a62-29e06132befc.jpeg)

大吾「こんなん簡単じゃな。任せときい」

カタカタカタカタカタカタカタカタカタカタカタカタ

大吾「ノブ〜できたぞ」
ノブ「おお！？はや早い。どれどれ見てみよか」
ノブ「ちゃんとボタンを押すと入力フォームが増えとるな」

![スクリーンショット 2022-07-26 8.41.44.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/a5c92672-d504-a26b-cc7f-0c87c06f6b06.jpeg)

ノブ「入力フォームに値を入れて確認してみようか」

![スクリーンショット 2022-07-26 8.44.00.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/011a8e38-2c2f-d70a-4d33-3b71a45eb7f9.jpeg)
![スクリーンショット 2022-07-26 8.44.04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/4f3e46ab-7655-72d7-9014-6114a12d0184.jpeg)

ノブ「おいおいおい！！」
ノブ「idが1の入力フォームに入れた入力値が追加ボタンを押して出てくるidが2の入力フォームに移動しとるやないカイ！」
大吾「そんなんワシは知らん」
ノブ「ちょっと大吾の書いたコードを見せてくれんか」
大吾「ほいっ」

```jsx
const Example: NextPage = () => {
  const [inputId, setInputId] = useState<number[]>([1]);
  const onClick = () => {
    const copy = [...inputId];
    copy.unshift(inputId.length + 1);
    setInputId(copy);
  };

  return (
    <ExampleContainer>
      {inputId.map((i, index) => (
        <div key={index}>
          <p>id:{i}</p>
          <input type="text" />
          <br />
        </div>
      ))}
      <button onClick={onClick} className="button">
        入力フォームを増やす
      </button>
    </ExampleContainer>
  );
};
export default Example;
```

ノブ「どれどれどれ」
ノブ「この部分で追加ボタンを押したときに配列の先頭に値が増えるようにしてるんやな」

```jsx
  const [inputId, setInputId] = useState<number[]>([1]);
  const onClick = () => {
    const copy = [...inputId];
    copy.unshift(inputId.length + 1);
    setInputId(copy);
  };
```

大吾「この部分の実装は、前にノブに教えたスプレット構文を使って配列のstateは更新してるぞ！」

https://qiita.com/KNR109/items/744da4e3db51779da985

ノブ「そうじゃなっ」
ノブ「この辺は問題なさそうやな」
ノブ「JSXに注目して見てよか」

```jsx
    <ExampleContainer>
      {inputId.map((i, index) => (
        <div key={index}>
          <p>id:{i}</p>
          <input type="text" />
          <br />
        </div>
      ))}
      <button onClick={onClick} className="button">
        入力フォームを増やす
      </button>
    </ExampleContainer>
```

ノブ「お！！！？？？」
ノブ「まてまてまてまて」
ノブ「JSX内のmapメソッドでindexをkeyに指定しないでくれぇ」

## JSX内のmapメソッドでindexをkeyに指定しないでくれぇ

ノブ「JSX内のこの部分に注目せぇ」
ノブ「ここで`input`をラップしている`div`タグの`key`に`index`を渡してるやろ」
ノブ「これが影響してさっきのような挙動になってしまってるんじゃ」

```jsx
{inputId.map((i, index) => (
        <div key={index}>
          <p>id:{i}</p>
          <input type="text" />
          <br />
        </div>
      ))}
```

ノブ「本来ならば新しく追加した入力フォーム(idが2)は空でないといけないはずなんだが`index`を`key`に指定した影響でidが1のフォームに入力した値が入ってきてしまっとる」

ノブ「ちなみにReactの公式ドキュメントでもこの部分については言及してるんじゃ」

https://ja.reactjs.org/docs/lists-and-keys.html

> 要素の並び順が変更される可能性がある場合、インデックスを key として使用することはお勧めしません。パフォーマンスに悪い影響を与え、コンポーネントの状態に問題を起こす可能性があります。

**【keyにindexを渡した影響で発生した挙動】**
![スクリーンショット 2022-07-26 9.31.53.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/2e82b7a5-1b4e-8494-5422-8ecfa2ad5e84.jpeg)

![スクリーンショット 2022-07-26 9.32.49.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/20b640ab-b42f-e9f4-85e6-5ac2381e3ebb.jpeg)

**【正しい挙動】**
![スクリーンショット 2022-07-26 9.33.04.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/c53c2782-6b82-ac95-9fe7-42e776e2787b.jpeg)

## なぜこのような挙動になるのか

大吾「ふむふむ。ノブが言いたいことはよう分かった。でもどうしてこんな挙動になってしまうんじゃ」
ノブ「配列の`index`って0から始まるじゃろ？」
ノブ「そのため`index`が0番目の要素に**idは1番です**という入力した値が紐付いてしまってるんじゃ」
ノブ「結果として新しく増えたidが2の入力フォーム(配列の0番目)に値がid:1で入力した値が入ってきてしまってるってことじゃな」

大吾「入力フォームのkeyが配列のindex番号に紐付いたことによってこの挙動になってしまってるわけか」

ノブ「画面で見てみるとこんな感じやな」

![index2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/ae09ecfe-a4c7-8d21-3f9f-7adc54d58375.png)

![index1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/d77b07ad-ce01-ec63-c69a-9af02c5ad4bc.png)


## 解決策

大吾「じゃあこれを解決するにはどうすれば良いんじゃ」
ノブ「`index`ではなく`id`などのユーニクな値を`key`に指定するれば解決する」
ノブ「今回で言えばこんな感じじゃ」

```js
const Example: NextPage = () => {
  const [inputId, setInputId] = useState<number[]>([1]);
  const onClick = () => {
    const copy = [...inputId];
    copy.unshift(inputId.length + 1);
    setInputId(copy);
  };

  return (
    <ExampleContainer>
      {inputId.map((i) => (
        <div key={i}> // keyにid(配列の値)を入れる
          <p>id:{i}</p>
          <input type="text" />
          <br />
        </div>
      ))}
      <button onClick={onClick} className="button">
        入力フォームを増やす
      </button>
    </ExampleContainer>
  );
};
export default Example;
```

大吾「おおっ！！正しく動いとる」


![スクリーンショット 2022-07-26 9.56.05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/32de0fa1-9f87-6cc6-c74d-b684b9cb3e09.jpeg)
![スクリーンショット 2022-07-26 9.56.22.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2695521/10e8a081-5121-dc4c-bf63-6d2b4f5f69d9.jpeg)

ノブ「APIとかのレスポンス値だと多くの場合はオブジェクト形式で`id`が付いて返ってくるからそれを`map`を利用する場合は`key`で指定するイメージじゃな」

```jsx
  const response = [
    {
      id: 1, // keyとしてidを指定する
      value: 'りんご',
    },
    {
      id: 2,
      value: 'ばなな',
    },
    {
      id: 3,
      value: 'ぶどう',
    },
  ];
```

大吾「なるほど。じゃあ`map`メソッド内で`index`はほとんど使っちゃダメんか？」

## mapメソッドのkeyにindexを指定してもよい場合

ノブ「`map`メソッドの`key`に`index`を指定してもよい場合を説明するぞ」
ノブ「React公式ドキュメントからリンクが貼られている**Index as a key is an anti-pattern**ではな」

>To help you decide, I put together three conditions which these examples > have in common:
> 1. the list and items are static–they are not computed and do not change;
> 2. the items in the list have no ids;
> 3. the list is never reordered or filtered.
>When all of them are met, you may safely use the index as a key.

https://robinpokorny.medium.com/index-as-a-key-is-an-anti-pattern-e0349aece318

日本語訳をすると

1. 配列と要素が静的で計算が行われない場合
2. 配列内の要素がIDを持ってない場合
3. 配列要素は順番が変わったりフィルターされることがない

コレらを全て満たしている場合に安全に`index`に`key`を使用することができる。

大吾「なるほどやな」
大吾「今後は`map`メソッドを使う際は`index`はできる限り使わんようにするわ」

## 最後に

ノブ「他にも色々な記事を書いているのでぜひ読んでくれぇぇぇぇぇ」

https://qiita.com/KNR109/items/e50c981e0518141be28f

https://qiita.com/KNR109/items/7cf6b24bed318dab5715

https://qiita.com/KNR109/items/201c133d6eeaabf675a7