React学習記録

【React入門】シンプルなTODOアプリの作り方 – 後編【削除・完了機能の実装】

yuya_react

はじめに

こんにちは、優也@月収100万を目指して月100時間勉強する男です。

前回・前々回の記事で、プロジェクトのセットアップ、タスク一覧の表示、タスクの追加機能を実装してきました。今回の後編では、削除機能と完了機能を実装して、TODOアプリを完成させます!

これでタスクを追加・削除・完了できる、実用的なTODOアプリが完成します。

これまでのおさらい

前編・中編で実装した機能:

  • ✅ プロジェクトのセットアップ
  • ✅ タスク一覧の表示
  • ✅ 未完了/完了タスクの分離表示
  • ✅ タスク追加機能

今回実装する機能

後編(今回)で実装する機能:

  • ✅ タスク削除機能
  • ✅ タスク完了機能
  • ✅ 完了タスクの表示

前回のコードを準備

まず、前回完成したコードから始めます。

import { useState } from 'react';

function App() {
  const [inputTask, setInputTask] = useState('');
  const [tasks, setTasks] = useState([
    { id: 1, taskName: '朝食を作る', completed: false },
    { id: 2, taskName: '買い物に行く', completed: false },
    { id: 3, taskName: 'ブログを書く', completed: true }
  ]);

  const addTask = () => {
    if (inputTask.trim() === '') return;

    const newId = tasks.length === 0 
      ? 1 
      : Math.max(...tasks.map(task => task.id)) + 1;
    
    const newTask = {
      id: newId,
      taskName: inputTask,
      completed: false
    };

    setTasks([...tasks, newTask]);
    setInputTask('');
  };

  const incompleteTasks = tasks.filter((task) => !task.completed);
  const completedTasks = tasks.filter((task) => task.completed);

  return (
    <div>
      <h1>TODO APP</h1>
      
      <h2>タスクの追加</h2>
      <div>
        <input 
          type="text"
          value={inputTask}
          onChange={(e) => setInputTask(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              addTask();
            }
          }}
          placeholder="タスクを入力"
        />
        <button type="button" onClick={addTask}>追加</button>
      </div>

      <h2>未完了タスク</h2>
      {incompleteTasks.length === 0 ? (
        <p>未完了タスクはありません</p>
      ) : (
        <table border="1">
          <thead>
            <tr>
              <th>タスク名</th>
            </tr>
          </thead>
          <tbody>
            {incompleteTasks.map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}

      <h2>完了タスク</h2>
      {completedTasks.length === 0 ? (
        <p>完了タスクはありません</p>
      ) : (
        <table border="1">
          <thead>
            <tr>
              <th>タスク名</th>
            </tr>
          </thead>
          <tbody>
            {completedTasks.map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

export default App;

ここから機能を追加していきます。

削除機能を実装

まず、タスクを削除する機能を作ります。

削除関数を追加

const deleteTask = (id) => {
  setTasks(tasks.filter((task) => task.id !== id));
};

コードの説明

tasks.filter((task) => task.id !== id)
  • filter: 条件に合う要素だけを残す
  • task.id !== id: 削除対象のID以外を残す
  • 結果: 指定したIDのタスクが除外された新しい配列

削除ボタンを追加

未完了タスクのテーブルに削除ボタンを追加します:

<tbody>
  {incompleteTasks.map((task) => (
    <tr key={task.id}>
      <td>{task.taskName}</td>
      <td>
        <button type="button" onClick={() => deleteTask(task.id)}>
          削除
        </button>
      </td>
    </tr>
  ))}
</tbody>

完了タスクのテーブルにも削除ボタンを追加します:

<tbody>
  {completedTasks.map((task) => (
    <tr key={task.id}>
      <td>{task.taskName}</td>
      <td>
        <button type="button" onClick={() => deleteTask(task.id)}>
          削除
        </button>
      </td>
    </tr>
  ))}
</tbody>

ポイント

onClick={() => deleteTask(task.id)}
  • アロー関数でラップする
  • task.idを引数として渡す
  • これでクリックされたタスクのIDがdeleteTaskに渡される

完了機能を実装

次に、タスクを完了状態にする機能を作ります。

完了関数を追加

const completeTask = (id) => {
  setTasks(
    tasks.map((task) =>
      task.id === id ? { ...task, completed: true } : task
    )
  );
};

コードの説明

tasks.map((task) =>
  task.id === id ? { ...task, completed: true } : task
)
  • map: すべてのタスクを処理
  • task.id === id: 対象のタスクかチェック
  • { ...task, completed: true }: 対象ならcompletedtrueに変更
  • : task: 対象外ならそのまま

スプレッド構文(...task)で既存のプロパティをコピーし、completedだけを上書きしています。

完了ボタンを追加

未完了タスクのテーブルに完了ボタンを追加します:

<tbody>
  {incompleteTasks.map((task) => (
    <tr key={task.id}>
      <td>{task.taskName}</td>
      <td>
        <button type="button" onClick={() => completeTask(task.id)}>
          完了
        </button>
      </td>
      <td>
        <button type="button" onClick={() => deleteTask(task.id)}>
          削除
        </button>
      </td>
    </tr>
  ))}
</tbody>

テーブルのヘッダーを調整

ボタンが増えたので、テーブルのヘッダーも調整します。

未完了タスク

<thead>
  <tr>
    <th>タスク名</th>
    <th>完了</th>
    <th>削除</th>
  </tr>
</thead>

完了タスク

<thead>
  <tr>
    <th>タスク名</th>
    <th>削除</th>
  </tr>
</thead>

完了タスクには「完了ボタン」は不要なので、削除ボタンのみです。

完成したコード全体

ここまでで完成したコードは以下の通りです:

import { useState } from 'react';

function App() {
  const [inputTask, setInputTask] = useState('');
  const [tasks, setTasks] = useState([
    { id: 1, taskName: '朝食を作る', completed: false },
    { id: 2, taskName: '買い物に行く', completed: false },
    { id: 3, taskName: 'ブログを書く', completed: true }
  ]);

  const addTask = () => {
    if (inputTask.trim() === '') return;

    const newId = tasks.length === 0 
      ? 1 
      : Math.max(...tasks.map(task => task.id)) + 1;
    
    const newTask = {
      id: newId,
      taskName: inputTask,
      completed: false
    };

    setTasks([...tasks, newTask]);
    setInputTask('');
  };

  const deleteTask = (id) => {
    setTasks(tasks.filter((task) => task.id !== id));
  };

  const completeTask = (id) => {
    setTasks(
      tasks.map((task) =>
        task.id === id ? { ...task, completed: true } : task
      )
    );
  };

  const incompleteTasks = tasks.filter((task) => !task.completed);
  const completedTasks = tasks.filter((task) => task.completed);

  return (
    <div>
      <h1>TODO APP</h1>
      
      <h2>タスクの追加</h2>
      <div>
        <input 
          type="text"
          value={inputTask}
          onChange={(e) => setInputTask(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              addTask();
            }
          }}
          placeholder="タスクを入力"
        />
        <button type="button" onClick={addTask}>追加</button>
      </div>

      <h2>未完了タスク</h2>
      {incompleteTasks.length === 0 ? (
        <p>未完了タスクはありません</p>
      ) : (
        <table border="1">
          <thead>
            <tr>
              <th>タスク名</th>
              <th>完了</th>
              <th>削除</th>
            </tr>
          </thead>
          <tbody>
            {incompleteTasks.map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
                <td>
                  <button type="button" onClick={() => completeTask(task.id)}>
                    完了
                  </button>
                </td>
                <td>
                  <button type="button" onClick={() => deleteTask(task.id)}>
                    削除
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      )}

      <h2>完了タスク</h2>
      {completedTasks.length === 0 ? (
        <p>完了タスクはありません</p>
      ) : (
        <table border="1">
          <thead>
            <tr>
              <th>タスク名</th>
              <th>削除</th>
            </tr>
          </thead>
          <tbody>
            {completedTasks.map((task) => (
              <tr key={task.id}>
                <td>{task.taskName}</td>
                <td>
                  <button type="button" onClick={() => deleteTask(task.id)}>
                    削除
                  </button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

export default App;

コード行数: 約110行

動作確認

ブラウザで以下を確認してください:

  1. ✅ タスクを追加できる
  2. ✅ 未完了タスクの「完了」ボタンで完了タスクに移動
  3. ✅ 未完了タスクの「削除」ボタンでタスクが削除される
  4. ✅ 完了タスクの「削除」ボタンでタスクが削除される

すべて動作していれば、TODOアプリ完成です! 🎉

まとめ

3回のシリーズでTODOアプリを完成させました!

実装した全機能:

  • ✅ タスク一覧の表示
  • ✅ 未完了/完了タスクの分離表示
  • ✅ タスクの追加
  • ✅ タスクの削除
  • ✅ タスクの完了

使った技術:

  • useState: タスクデータと入力の管理
  • map: 配列をリスト表示
  • filter: 条件に合うタスクを抽出、削除
  • スプレッド構文: 配列への追加、オブジェクトの更新
  • onChange/onClick: ユーザー操作の処理

コード行数: 約110行

たった110行のコードで、実用的なTODOアプリが完成しました。これがReactの力です!

次回からは、配列操作の詳細やよくあるエラーについて解説していきます。


この記事のシリーズ:

  1. 【React入門】未経験からReactを学んで最初のTODOアプリを作った話
  2. 【2025年版】Viteで始めるReact開発環境の構築方法
  3. 【React基礎】useStateとは?初心者向けに分かりやすく解説
  4. 【React】イベントハンドラの書き方完全ガイド
  5. 【React】map関数でリストをレンダリングする方法
  6. 【React入門】TODOアプリの作り方 – 前編
  7. 【React入門】TODOアプリの作り方 – 中編
  8. 【React入門】TODOアプリの作り方 – 後編 ← 今ここ
  9. 【実例で学ぶ】Reactで配列のstateを更新する5つのパターン(次回)

あとがき

月100時間の学習時間の中で、今月はReactに集中して取り組んでいます。Instagramでは、フリーランスとして月収100万円を目指す道のりを、リアルタイムで発信していきますので、ぜひフォローしてください!

Instagram: @hooded_yuya_100man

ABOUT ME
記事URLをコピーしました