Chapter 12無料公開

タスクの完了/未完了を操作できるようにする - Todo の仕様を考える (その 3)

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;
};

TODO 型オブジェクトには checked プロパティが必須となったため、 handleOnSubmit() メソッドを更新します。

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

    const newTodo: Todo = {
      value: text,
      id: new Date().getTime(),
      // 初期値(todo 作成時)は false
      checked: false,
    };

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

それぞれの todo の前へ、完了/未完了を操作をするためのチェックボックスを置きます。

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

2021-04-25 10.07.23.png

チェックボックスがチェックされたときのコールバック関数を作成する

前々章の handleOnEdit() コールバック関数とパターンは同じです。

どの todo がチェックされたのか特定するための idonChange イベントの結果を引数として受け取り、その todo 型オブジェクトchecked プロパティを反転させます。

src/App.tsx
  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);
  };

チェックボックスのイベントへ紐付けましょう。

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

2021-04-25 10.18.15.png

このままではチェック済みのタスクも編集できてしまうので、チェック済みの項目は入力フォームを無効にします。

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

2021-04-25 10.22.45.png

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

type Todo = {
  value: string;
  readonly id: number;
  checked: 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,
    };

    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);
  };

  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"
                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)}
              />
            </li>
          );
        })}
      </ul>
    </div>
  );
};