1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[ Warning: Each child in a list should have a unique "key" prop.]を 解決する時、悪い習慣を注意

Posted at

課題

{list.map((todo, index) => (<ToDo  {...todo} />))}

Reactで配列を表示する時は、子コンポーネントにキーを定義しなければ以下警告が出ます。

TodoList.js:65 Warning: Each child in a list should have a unique "key" prop.

そして、皆さんが以下の解決をしがちです。
Mapのindexをキーにします

{list.map((todo, index) => (<ToDo key={index} {...todo} />))}

->こちらは悪い習慣

そうすれば、恐らく以下のエラーが発生します。(詳細コードが下の「その他」にあります)

  • 追加が正しくない
  • ソートが正しくない

ScreenRecording2024-10-14at17.50.25-ezgif.com-video-to-gif-converter.gif

修正後
{list.map((todo) => (<ToDo key={todo.id} {...todo} />))}

oke.gif

解説

まずReact Diffing理解

コンポーネント更新される場合は、
1. 親コンポーネント変更

<div>
  <Counter />
</div>
 
<span>
  <Counter />
</span>

// 親コンポーネントをdiv->spanに変更するため。

2. コンポーネント自体に性質が変更

<div className="before" title="stuff" />

<div className="after" title="stuff" />

//className 変更

Demoを分析

KeyにMapのindex値を利用

<input/> にはMapのindex値が変更されず、<input/> も更新されません。
一方、変更されするコンポネートは idのlabelとcreatedAtのlabelです。
上記のデモにこれらの位置が変更されますが、<input/> 位置が変更されません。

Screenshot 2024-10-14 at 17.22.04.png
(補足:更新のものが緑色)

KeyにidのTodoを利用

ソートや追加の時に、Todoのidは変更されますので、<tr/>が全体更新されます。

Screenshot 2024-10-14 at 17.32.32.png

結論

・Keyが必要
・Keyの値が雄一無二
・仕方無い場合はMapのindexを利用

その他

上記のコード

import React, { useState } from "react";


// Functional ToDo Component
const ToDo = ({ id, createdAt }) => (
  <tr>
    <td>
      <label>{id}</label>
    </td>
    <td>
      <input />
    </td>
    <td>
      <label>{createdAt.toTimeString()}</label>
    </td>
  </tr>
);

// Functional ToDoList Component
const ToDoList = () => {
  const [list, setList] = useState([{ id: 1, createdAt: new Date() }]);
  const [toDoCounter, setToDoCounter] = useState(1);

  // Sorting by earliest date
  const sortByEarliest = () => {
    const sortedList = [...list].sort((a, b) => a.createdAt - b.createdAt);
    setList(sortedList);
  };

  // Sorting by latest date
  const sortByLatest = () => {
    const sortedList = [...list].sort((a, b) => b.createdAt - a.createdAt);
    setList(sortedList);
  };

  // Adding a new ToDo to the end of the list
  const addToEnd = () => {
    const newDate = new Date();
    const nextId = toDoCounter + 1;
    setList([...list, { id: nextId, createdAt: newDate }]);
    setToDoCounter(nextId);
  };

  // Adding a new ToDo to the start of the list
  const addToStart = () => {
    const newDate = new Date();
    const nextId = toDoCounter + 1;
    setList([{ id: nextId, createdAt: newDate }, ...list]);
    setToDoCounter(nextId);
  };

  return (
    <div>
      <code>key=id</code>
      <br />
      <button onClick={addToStart}>Add New to Start</button>
      <button onClick={addToEnd}>Add New to End</button>
      <button onClick={sortByEarliest}>Sort by Earliest</button>
      <button onClick={sortByLatest}>Sort by Latest</button>
      <table>
        <thead>
          <tr>
            <th>ID</th>
            <th />
            <th>Created At</th>
          </tr>
        </thead>
        <tbody>
          {list.map((todo,index ) => (
            <ToDo key={index} {...todo} />
          ))}
        </tbody>
      </table>
    </div>
  );
};

export default ToDoList;

参考
https://www.geeksforgeeks.org/what-is-diffing-algorithm/
https://www.geeksforgeeks.org/explain-dom-diffing/?ref=oin_asr1

最後まで読んで頂いて有り難うございます。:bow:
役に立つを感じしたらハートやコメントやを残ってください:bow:

1
0
0

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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?