Chapter 10無料公開

登録済みの todo を編集可能にする

Kei Touge
Kei Touge
2021.10.23に更新

todo を入力フォーム化する

すでに登録済みの todo を編集可能にするにするため、<li></li> タグ内で展開される各項目の todo.value<input /> タグでラップします。

src/App.tsx
      <ul>
        {todos.map((todo) => {
          return (
            <li key={todo.id}>
              <input
                type="text"
                value={todo.value}
                onChange={(e) => e.preventDefault()}
              />
            </li>
          );
        })}
      </ul>

ここでも onChange イベントではとりあえず e.preventDefault() しているので入力しても何の変化も起きません。

2021-04-25 10.04.48.png

登録済み todo が編集された時のコールバック関数を作成する

編集されたタスクの内容を適用し、そのタスクを古いものから新しいものへ入れ替えた状態に todos ステート を更新しなければいけません。

ステートを更新するコールバック関数には以下の要件が求められます。

  • どの todo が編集されたのか特定するため、その todoid を引数として受け取る
  • e.target.value(onChange イベントの結果)を書き換え後の todo.value の値とするために第2引数として受け取る
  • 編集後の todo を含む Todo 型の配列todos ステート を書き換える
src/App.tsx
  const handleOnEdit = (id: number, value: string) => {
    /**
     * 引数として渡された todo の id が一致する
     * todos ステート(のコピー)内の todo の
     * value プロパティを引数 value (= e.target.value) に書き換える
     */
    const newTodos = todos.map((todo) => {
      if (todo.id === id) {
        todo.value = value;
      }
      return todo;
    });

    // todos ステートを更新
    setTodos(newTodos);
  };

Array.prototype.map() は、配列のコピーに変更を加えた結果からなる新しい配列を生成する非破壊的メソッドです。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map

上のコールバック関数を <input onChange={} /> イベントに紐付けます。

src/App.tsx
      <ul>
        {todos.map((todo) => {
          return (
            <li key={todo.id}>
              <input
                type="text"
                value={todo.value}
                onChange={(e) => handleOnEdit(todo.id, e.target.value)}
               />
            </li>
          );
        })}
      </ul>

ステートのイミュータビリティは保たれているか?

では、上の処理を行うことによってもステートのイミュータビリティimmutability, 不変性)は保たれているのでしょうか?

結論から言うと、この手法ではイミュータビリティを保つことはできません。

上のコードの newTodos 配列を作成した(かつ todos ステートを更新する)の todos ステートの値を確認してみましょう。

  const handleOnEdit = (id: number, value: string) => {
    const newTodos = todos.map((todo) => {
      if (todo.id === id) {
        todo.value = value;
      }
      return todo;
    });

    // todos ステートが書き換えられていないかチェック
    console.log('=== Original todos ===');
    todos.map((todo) => console.log(`id: ${todo.id}, value: ${todo.value}`));

    setTodos(newTodos);
  };

結果は以下のようになります。

setTodos(newTodos) が実行される前に todos ステート配列が直接ミューテートされてしまっています。

Array.prototype.map() は、「新しい配列を生成する非破壊的メソッド」であるはずなのに何故なのでしょうか?