LoginSignup
67
38

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-07-28

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

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

スクリーンショット 2022-07-26 8.39.09.jpg

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

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

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

スクリーンショット 2022-07-26 8.41.44.jpg

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

スクリーンショット 2022-07-26 8.44.00.jpg
スクリーンショット 2022-07-26 8.44.04.jpg

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

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;

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

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

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

ノブ「そうじゃなっ」
ノブ「この辺は問題なさそうやな」
ノブ「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タグのkeyindexを渡してるやろ」
ノブ「これが影響してさっきのような挙動になってしまってるんじゃ」

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

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

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

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

【keyにindexを渡した影響で発生した挙動】
スクリーンショット 2022-07-26 9.31.53.jpg

スクリーンショット 2022-07-26 9.32.49.jpg

【正しい挙動】
スクリーンショット 2022-07-26 9.33.04.jpg

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

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

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

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

index2.png

index1.png

解決策

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

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
スクリーンショット 2022-07-26 9.56.22.jpg

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

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

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

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

ノブ「mapメソッドのkeyindexを指定してもよい場合を説明するぞ」
ノブ「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.

日本語訳をすると

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

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

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

最後に

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

67
38
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
67
38