Chapter 13無料公開

登録済みの todo を削除可能にする - Todo の仕様を考える (その 4)

Kei Touge
Kei Touge
2021.11.20に更新

Todo 型オブジェクトの再拡張ふたたび

タスクの削除/未削除を示すフラグを Todo 型に追加しましょう。
これも true or false を表すプロパティなので型は Boolean 型 となります。

src/App.tsx
type Todo = {
  value: string;
  readonly id: number;
  checked: boolean;
  removed: boolean;
};

前章の checked のときと同じく、 handleOnSubmit() メソッドを更新する必要があります。

src/App.tsx
    if (!text) return;

    const newTodo: Todo = {
      value: text,
      id: new Date().getTime(),
      checked: false,
      removed: false,
    };

    setTodos([newTodo, ...todos]);

削除ボタンの追加

それぞれの入力フォームの後ろへ削除ボタンを追加します。

App.tsx
  return (
    <li key={todo.id}>
      <input
        type="checkbox"
        checked={todo.checked}
        onChange={() => handleOnCheck(todo.id, todo.checked)}
      />
      <input
        type="text"
        disabled={todo.checked}
        value={todo.value}
        onChange={(e) => handleOnEdit(todo.id, e.target.value)}
      />
      <button onClick={() => console.log('removed!')}>削除</button>
    </li>
  );

2021-04-25 10.32.55.png

削除ボタンがクリックされたときのコールバック関数を作成する

これも前章の handleOnChecked() とまったく同じパターンです。
同様に onClick イベントへ紐付けします。

src/App.tsx
  const handleOnRemove = (id: number, removed: boolean) => {
    const deepCopy: Todo[] = JSON.parse(JSON.stringify(todos));

    const newTodos = deepCopy.map((todo) => {
      if (todo.id === id) {
        todo.removed = !removed;
      }
      return todo;
    });

    setTodos(newTodos);
  };

すでに削除済みかどうかを可視化するため、todo.removed の値によってボタンのラベルを入れ替えましょう。

src/App.tsx
    <button onClick={() => handleOnRemove(todo.id, todo.removed)}>
      {todo.removed ? '復元' : '削除'}
    </button>

削除されたアイテムは改変できないようにするため、チェックボックスと入力フォームも無効化します。

src/App.tsx
  <input
    type="checkbox"
    disabled={todo.removed}
    checked={todo.checked}
    onChange={() => handleOnCheck(todo.id, todo.checked)}
  />
  <input
    type="text"
    disabled={todo.checked || todo.removed}
    value={todo.value}
    onChange={(e) => handleOnEdit(todo.id, e.target.value)}
  />

2021-04-25 10.51.02.png

この章のソースコード全文 App.tsx
src/App.tsx
import { useState } from 'react';

type Todo = {
  value: string;
  readonly id: number;
  checked: boolean;
  removed: boolean;
};

export const App = () => {
  const [text, setText] = useState('');
  const [todos, setTodos] = useState<Todo[]>([]);

  const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value);
  };

  const handleOnSubmit = () => {
    if (!text) return;

    const newTodo: Todo = {
      value: text,
      id: new Date().getTime(),
      checked: false,
      removed: false,
    };

    setTodos([newTodo, ...todos]);
    setText('');
  };

  const handleOnEdit = (id: number, value: string) => {
    const deepCopy: Todo[] = JSON.parse(JSON.stringify(todos));

    const newTodos = deepCopy.map((todo) => {
      if (todo.id === id) {
        todo.value = value;
      }
      return todo;
    });

    setTodos(newTodos);
  };

  const handleOnCheck = (id: number, checked: boolean) => {
    const deepCopy: Todo[] = JSON.parse(JSON.stringify(todos));

    const newTodos = deepCopy.map((todo) => {
      if (todo.id === id) {
        todo.checked = !checked;
      }
      return todo;
    });

    setTodos(newTodos);
  };

  const handleOnRemove = (id: number, removed: boolean) => {
    const deepCopy: Todo[] = JSON.parse(JSON.stringify(todos));

    const newTodos = deepCopy.map((todo) => {
      if (todo.id === id) {
        todo.removed = !removed;
      }
      return todo;
    });

    setTodos(newTodos);
  };

  return (
    <div>
      <form
        onSubmit={(e) => {
          e.preventDefault();
          handleOnSubmit();
        }}
      >
        <input type="text" value={text} onChange={(e) => handleOnChange(e)} />
        <input type="submit" value="追加" onSubmit={handleOnSubmit} />
      </form>
      <ul>
        {todos.map((todo) => {
          return (
            <li key={todo.id}>
              <input
                type="checkbox"
                disabled={todo.removed}
                checked={todo.checked}
                onChange={() => handleOnCheck(todo.id, todo.checked)}
              />
              <input
                type="text"
                disabled={todo.checked || todo.removed}
                value={todo.value}
                onChange={(e) => handleOnEdit(todo.id, e.target.value)}
              />
              <button onClick={() => handleOnRemove(todo.id, todo.removed)}>
                {todo.removed ? '復元' : '削除'}
              </button>
            </li>
          );
        })}
      </ul>
    </div>
  );
};