技術

【基礎編】React + TypeScript でシンプルな TODO アプリを実装②

レザボア・コンサルティングの中西です。

本記事では、 基礎編①で実装した React + TypeScript でシンプルな TODO アプリを、さらに拡張する手順を紹介します。

【基礎編】React + TypeScript でシンプルな TODO アプリを実装①

基礎編①での実装内容の概要

基礎編①では、以下の内容を紹介しました。

React のセットアップ

create-react-app コマンドを使用して、TypeScript テンプレートを用いた React アプリケーションの雛形を作成し、Material UI をインストールしてデザインを整えます。

単一の TODO コンポーネントの作成

TODO アイテムを表示するための TodoItem コンポーネントを作成します。
このコンポーネントは、Material UI の Card コンポーネントを使用してデザインされます。

TODO コンポーネントのリストの作成

複数の TODO アイテムを管理するための TodoList コンポーネントを作成します。
このコンポーネントでは、新しい TODO アイテムを追加するための入力欄とボタンが配置され、useState フックを使用して状態管理を行います。

TODO リストを表示

App.tsx を修正して、作成した TodoList コンポーネントを画面に表示します。

基礎編②での実装内容

基礎編①で、タスクを追加する仕組みと簡単な状態管理を紹介しました。
ここからは、応用編として、TODO の編集・完了機能を実装する方法を説明します。

実施する内容について

コードの紹介の前に、簡単に、今回の実装内容を紹介します。
この内容を前提とした上でコードを確認することで、より理解が深まると思います。

TODO の状態管理

TODO の編集・完了機能を実装するために、まずは TODO の状態を管理するためのプロパティを追加します。

具体的には、各 ToDo アイテムに対して、編集中かどうかを示す isEditing と、完了したかどうかを示す isCompleted の2つのプロパティを追加します。

ToDoItem コンポーネントの修正

TodoItem コンポーネントを修正して、編集・完了機能を追加します。

編集機能では、テキストフィールドを表示して TODO のテキストを更新できるようにします。
完了機能では、チェックボックスを利用して TODO の完了状態を切り替えられるようにします。

TodoList コンポーネントの修正

TodoList コンポーネントでは、TODO アイテムの編集と完了の状態変更を管理するための関数を追加します。
また、これらの関数を TodoItem コンポーネントに渡して、アイテムの状態を更新できるようにします。

事前準備

画面の見栄えを良くするために、マテリアルアイコンも使用しましょう。
以下のコマンドをプロジェクトフォルダのルートで実行し、アイコンを使えるようにしておきます。

npm install @mui/icons-material

実装

1. TodoItem.tsx の修正

ToDoItem コンポーネントの修正 の章に記載した内容を実装します。

改修前の TodoItem コンポーネントでは、単一のTODOアイテムを表示するための基本的な構造を持っています。

この段階のコンポーネントは、渡された todo プロパティ(オブジェクト)の text 値を表示するために CardCardContent を使用してデザインを整えています。
この時点では、TODOアイテムの編集や完了の状態を扱うロジックは含まれていません。

TodoItem.tsx を以下のコードで上書きします。

import React, { useState } from 'react';
import { Card, TextField, Checkbox, IconButton, Typography } from '@mui/material';
import EditIcon from '@mui/icons-material/Edit';
import SaveIcon from '@mui/icons-material/Save';
import CancelIcon from '@mui/icons-material/Cancel';

interface TodoItemProps {
  todo: {
    id: number;
    text: string;
    isEditing: boolean;
    isCompleted: boolean;
  };
  toggleComplete: (id: number) => void;
  startEditing: (id: number) => void;
  cancelEditing: (id: number) => void;
  saveEdit: (id: number, newText: string) => void;
}

const TodoItem: React.FC<TodoItemProps> = ({ todo, toggleComplete, startEditing, cancelEditing, saveEdit }) => {
  const [editText, setEditText] = useState(todo.text);

  return (
    <Card sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', margin: '8px 0' }}>
      <Checkbox checked={todo.isCompleted} onChange={() => toggleComplete(todo.id)} />
      {todo.isEditing ? (
        <TextField value={editText} onChange={(e) => setEditText(e.target.value)} />
      ) : (
        <Typography sx={{ textDecoration: todo.isCompleted ? 'line-through' : 'none' }}>{todo.text}</Typography>
      )}
      <div>
        {todo.isEditing ? (
          <>
            <IconButton onClick={() => saveEdit(todo.id, editText)}><SaveIcon /></IconButton>
            <IconButton onClick={() => cancelEditing(todo.id)}><CancelIcon /></IconButton>
          </>
        ) : (
          <IconButton onClick={() => startEditing(todo.id)}><EditIcon /></IconButton>
        )}
      </div>
    </Card>
  );
};

export default TodoItem;

かなり大幅に改修することになりますので、git 等で差分を確認することをおすすめします。

変更点:状態管理の追加

isEditing: boolean;
isCompleted: boolean;

TODOアイテムに isEditing と isCompleted の二つの状態を管理するためのプロパティを追加しました。
これにより、各TODOアイテムが編集中かどうか、完了しているかどうかを把握できるようになります。

変更点:編集機能の実装

まずプロパティに以下のアクションを定義し、編集、キャンセル、保存の処理を定義できるようにします。
引数にも追加する必要があります。

startEditing: (id: number) => void;
cancelEditing: (id: number) => void;
saveEdit: (id: number, newText: string) => void;

また、編集時の状態を useState フックを用いて管理します。
タイトルの編集機能なので text のみ管理しています。

const [editText, setEditText] = useState(todo.text);

return で返却する JSX は、todo.isEditing ? で編集モードか否かで表示を切り替えています。
編集の保存とキャンセルにはマテリアルUIの IconButton を使用し、デザインを整えています。

変更点:完了機能の実装

Checkbox を追加して、TODOアイテムの完了状態を切り替えられるようにしました。
チェックボックスの状態は isCompleted プロパティによって管理されます。

2. TodoList.tsx の修正

TodoItem.tsx でプロパティを更新したので、上位のコンポーネントである TodoList.tsx にも修正を加えていきます。

TodoList.tsx を以下のコードで上書きします。

import React, { useState } from 'react';
import TodoItem from './TodoItem';
import { TextField, Button } from '@mui/material';

interface Todo {
  id: number;
  text: string;
  isEditing: boolean;
  isCompleted: boolean;
}

const TodoList: React.FC = () => {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [input, setInput] = useState('');

  const addTodo = () => {
    setTodos([...todos, { id: Date.now(), text: input, isEditing: false, isCompleted: false }]);
    setInput('');
  };

  const toggleComplete = (id: number) => {
    setTodos(todos.map(todo => todo.id === id ? { ...todo, isCompleted: !todo.isCompleted } : todo));
  };

  const startEditing = (id: number) => {
    setTodos(todos.map(todo => todo.id === id ? { ...todo, isEditing: true } : todo));
  };

  const cancelEditing = (id: number) => {
    setTodos(todos.map(todo => todo.id === id ? { ...todo, isEditing: false } : todo));
  };

  const saveEdit = (id: number, newText: string) => {
    setTodos(todos.map(todo => todo.id === id ? { ...todo, text: newText, isEditing: false } : todo));
  };

  return (
    <div>
      <TextField label="TODOを追加" value={input} onChange={(e) => setInput(e.target.value)} />
      <Button onClick={addTodo}>追加</Button>
      {todos.map((todo) => (
        <TodoItem key={todo.id} todo={todo} toggleComplete={toggleComplete} startEditing={startEditing} cancelEditing={cancelEditing} saveEdit={saveEdit} />
      ))}
    </div>
  );
};

export default TodoList;

変更点:状態変更関数の追加

TODOアイテムの編集開始、編集キャンセル、編集内容の保存、完了状態の切り替えを行うための関数を追加しました。 setTodos を使ってTODOリストの状態を更新します。

変更点:状態管理の拡張

各ToDoアイテムに isEditingisCompleted プロパティを追加して、編集状態と完了状態を管理できるようにしました。

TodoItem.tsx の TODO と同じようなプロパティを要するので、実際のプロジェクトを意識するなら types 等にまとめるのも良いですね。

変更点:子コンポーネントへのプロパティ渡し

TodoItem コンポーネントに対して、状態変更を行う関数(toggleComplete, startEditing, cancelEditing, saveEdit)をプロパティとして渡します。

これにより、各TODOアイテムが自身の状態を更新できるようになります。

動作確認

以下のコマンドを実行します。

npm start

アプリケーションが Port 3000 で起動していると思います。

http://localhost:3000/ にアクセスします。

[タスク追加時の見た目]
react-typescript-todo-app-02_1
チェックボックス、編集アイコンが表示されるようになりました。

[タスクの完了]
react-typescript-todo-app-02_2
チェックボックスをONにすると、タスクに打ち消し線が引かれるようになりました。

[タスクの編集]
react-typescript-todo-app-02_3
編集ボタンを押すと、タスク名を編集できるようになり、右側のアイコンが保存とキャンセルアイコンに変わりました。

[タスクの保存]
react-typescript-todo-app-02_4
保存した変更内容が反映されました。

おわりに

基礎編①の状態から、かなり TODO 管理アプリケーションっぽくなりましたね。

このように、React は動的な UI を素早く実現できる強力なライブラリです。

実装の参考になりましたら幸いです。

関連記事

TOP