Tips

【React】データのidをlengthで配列の要素数(長さ)にしてはダメ!

  • このエントリーをはてなブックマークに追加

今回はReactでToDoアプリなどを作成する際の、タスクにidを付与する方法について考えてみましょう。

初学者向けの教材やYoutube動画で紹介される、配列の長さをidとして利用するのは簡単で便利そうに見えます。

しかしこの方法は実務では危険な手法となっているのを初学者の方はご存知ないのではないでしょうか?

今回は配列の長さをidとして使用することがなぜ問題なのか、そして代わりにどのような手法を使うべきかについて解説していきます。

また動画もあるので必要な方は以下よりご視聴ください。

IDを配列の長さにするとマズい理由

例題として簡単なTODOアプリをReactで作ってみます。

import "./App.css";
import FetchApi from "./screens/wrongId/Todo.js";
function App() {
  return (
    <div className="App">
      <Todo />
    </div>
  );
}

export default App;
import React, { useState } from "react";

const Todo = () => {
  const [inputText, setInputText] = useState("");
  const [taskList, setTaskList] = useState([]);

  const handleChange = (e) => {
    setInputText(e.target.value);
  };

  const addTask = (e) => {
    e.preventDefault();
    setTaskList([
      ...taskList,
      {
        id: taskList.length,
        text: inputText,
      },
    ]);
    setInputText("");
  };

  const handleDelete = (id) => {
    let array = taskList.filter((task) => task.id !== id);
    setTaskList(array);
  };

  return (
    <>
      <form>
        <input type="text" onChange={handleChange} value={inputText} />
        <button onClick={(e) => addTask(e)}>追加</button>
      </form>
      <div>
        {taskList.map((task) => (
          <div key={task.id}>
            <div>
              {task.text}:{task.id}
              <button onClick={() => handleDelete(task.id)}>削除</button>
            </div>
          </div>
        ))}
      </div>
    </>
  );
};

export default Todo;

画面上に表示されるのは入力欄と追加ボタンになります。

入力欄にタスクを入力して追加ボタンをクリックするとタスクが順番に追加される仕組みです。

また追加タスクには削除ボタンが付いていて、削除ボタンをクリックすると画面上から消える機能も実装しました。

実務では表示しないことが多いですがタスクのIDも画面上に表示するようにしています。

一覧にあるタスクのIDが確認しやすいようにするためです。

それぞれのタスクのIDは0から始まる連番になっています。

タスクの全体を配列taskListで管理していて、taskList.lengthとすることで「配列の長さ(要素数)=追加するタスクのID」としています。

これにより順番の数字がIDと振られるので一意なIDになるわけです。

初学者向けの教材やYoutube動画でよく紹介される手法です。

import React, { useState } from "react";

const Todo = () => {
  const [inputText, setInputText] = useState("");
  const [taskList, setTaskList] = useState([]);

  const handleChange = (e) => {
    setInputText(e.target.value);
  };

  const addTask = (e) => {
    e.preventDefault();
    setTaskList([
      ...taskList,
      {
       // タスクのIDは配列の長さ(要素数)にすることで連番になる
        id: taskList.length,
        text: inputText,
      },
    ]);
    setInputText("");
  };

  const handleDelete = (id) => {
    let array = taskList.filter((task) => task.id !== id);
    setTaskList(array);
  };

  return (
    <>
      <form>
        <input type="text" onChange={handleChange} value={inputText} />
        <button onClick={(e) => addTask(e)}>追加</button>
      </form>
      <div>
        {taskList.map((task) => (
          <div key={task.id}>
            <div>
              {task.text}:{task.id}
              <button onClick={() => handleDelete(task.id)}>削除</button>
            </div>
          </div>
        ))}
      </div>
    </>
  );
};

export default Todo;

しかし問題はここからです、一度追加したタスクを削除することをやってみます。

上図のなかで「資料作成」を削除すると以下のようになります。

タスクのIDは配列の長さだったはずですが、タスクを削除すると連番ではなくなってしまいました。

正直IDは連番でなくても問題なかったりするのですが、現在の状態からもう一度「資料作成」を追加するとどうなるか見てみましょう。

さらに新しいタスクを追加してみます。

こんな感じでタスクのIDが重複したものになっていませんね、これがIDを配列の長さにしてはマズい理由です。

この状態で重複している「資料作成」「明日の準備」を削除したいとします。

まず「資料作成」の削除ボタンをクリックすると削除できたのですが、「明日の準備」の削除ボタンをクリックしても削除できません。

またコンソールにエラーが表示されています。

エラー文の日本語訳としては「3 という同じキーを持つ2つの子要素が検出されました。コンポーネントが更新されても同じものであるため、キーはユニークである必要があります。ユニークでないキーは、子要素が複製または省略される可能性があり、この動作はサポートされておらず、将来のバージョンで変更される可能性があります。」というものです。

IDが重複していることが原因で、重複した理由は途中で削除したことで配列の長さが変更されて連番が狂ったことにあります。

IDの指定で配列の長さは便利なのですが、配列の長さが途中で変わることを想定しないといけないのです。

ユニークなIDを付与しよう

データのIDを配列の長さで指定すると良くないことを体験してもらいました。

解決方法としては配列の長さではなく、言葉通りユニークなIDで指定することです。

ユニークなIDを設定する方法はライブラリも含めるといろいろ方法があるのでプロジェクトに適した方法を使ってもらえれば大丈夫です。

今回はJavaScriptの標準クラスであるcryptoのrandomUUIDというメソッドを使ってみます。

import React, { useState } from "react";

const Todo = () => {
  const [inputText, setInputText] = useState("");
  const [taskList, setTaskList] = useState([]);

  const handleChange = (e) => {
    setInputText(e.target.value);
  };

  const addTask = (e) => {
    e.preventDefault();
    setTaskList([
      ...taskList,
      {
       // ここを修正
        id: crypto.randomUUID(),
        text: inputText,
      },
    ]);
    setInputText("");
  };

  const handleDelete = (id) => {
    let array = taskList.filter((task) => task.id !== id);
    setTaskList(array);
  };

  return (
    <>
      <form>
        <input type="text" onChange={handleChange} value={inputText} />
        <button onClick={(e) => addTask(e)}>追加</button>
      </form>
      <div>
        {taskList.map((task) => (
          <div key={task.id}>
            <div>
              {task.text}:{task.id}
              <button onClick={() => handleDelete(task.id)}>削除</button>
            </div>
          </div>
        ))}
      </div>
    </>
  );
};

export default Todo;

もう一度ブラウザをリロードして動作確認してみましょう。

連番と違って複雑な文字の組み合わせがIDになっていますね。

ユニークIDもプロジェクトによっては文字数、使用する文字の種類などルールを決めることが多いので上図はあくまで参考までに見ておきましょう。

続いて先ほど同様に「資料作成」を削除します。

もう一度「資料作成」を追加します。

連番ではないので見づらいですが、最初に追加したときと一度削除して再度追加したときとで「資料作成」のIDは別のものになっています。

それぞれのタスクのIDが重複していないので削除するときは対象の1個しか削除されません。

IDはデータを管理するための大事な要素です。

僕らの生活で言うところのマイナンバーや戸籍のようなものと同じです。

初学者のうちは動作させることを目標に作ってもらえれば大丈夫ですが、いずれ実務を想定したプログラムを考えることになるので少しずつ意識していきましょう。

また今回参考にした本はこちらになります。

良ければこちらからどうぞ。

  • このエントリーをはてなブックマークに追加