モダンJavaScriptの基礎から始める挫折しないためのReact入門 60日コース | Stateを使いこなす - DAY 30:State総合制作

React 60日で身につけるReact
スポンサーリンク
スポンサーリンク
  1. DAY 30:State総合制作 前半 ― TODOアプリ Ver.1 を作りながら復習する
  2. アプリ全体の設計をイメージする
    1. どんなStateが必要かを先に考える
  3. タスク追加機能を作る
    1. 入力欄と「追加」ボタンを用意する
  4. タスク一覧表示を作る
    1. map()で配列を画面に変換する
  5. 完了機能を作る(状態の切り替え)
    1. 完了ボタンと完了状態の表示
  6. 削除機能を作る
    1. 削除ボタンとfilter()によるState更新
  7. TODOアプリ Ver.1(前半完成版テンプレート)
  8. DAY 30 前半のまとめ
  9. DAY 30:State総合制作 中盤 ― 「動くTODOアプリ」を設計目線で理解する
  10. TODOアプリ Ver.1のState設計をもう一度見直す
    1. どんなStateを持っているかを言語化する
  11. タスク追加を「安全なパターン」として固定する
    1. 追加処理のステップを言葉で追う
  12. タスク一覧表示を「mapの基本パターン」として理解する
    1. mapで「データ → 画面」を変換する
  13. 完了機能を「一部更新の標準パターン」として定着させる
    1. 完了状態の切り替えの中身を丁寧に読む
  14. 削除機能を「filterの基本パターン」として理解する
    1. 削除は「残したいものだけを残す」発想
  15. TODOアプリ Ver.1 中盤版テンプレート(設計目線でコメント付き)
  16. DAY 30 中盤のまとめ ― 「TODO Ver.1」を自分の基本パターンにする
  17. DAY 30:State総合制作(後半)― TODOアプリ Ver.1 を「自分の定番パターン」に仕上げる
  18. アプリ全体のStateをもう一度整理する
    1. 3つのStateが何を表しているかを明確にする
  19. タスク追加機能を「再利用可能な型」として固める
    1. 追加処理の流れをパターンとして覚える
  20. タスク一覧表示を「map+keyの基本形」として固定する
    1. mapで「データ → JSX」に変換する
  21. 完了機能を「一部更新の代表パターン」として仕上げる
    1. 完了状態切り替えのコードを、もう一度じっくり読む
  22. 削除機能を「filterの標準パターン」として固める
    1. 削除処理を「残したいものだけを残す」視点で理解する
  23. TODOアプリ Ver.1(後半版)― 少しだけ整えた完成テンプレート
  24. DAY 30 後半のまとめ ― 「TODO Ver.1」を自分の基本パターンとして持つ

DAY 30:State総合制作 前半 ― TODOアプリ Ver.1 を作りながら復習する

DAY 30では、これまで学んできた State・配列・オブジェクト・map・filter・一部更新 を総動員して、 シンプルな TODOアプリ Ver.1 を実際に作っていきます。

Ver.1の機能は次の4つです。

  • タスク追加
  • タスク一覧表示
  • 完了状態の切り替え
  • 削除

ここまでの学習の「総合テスト」のような位置づけですので、 一つひとつの機能を、落ち着いてステップバイステップで組み立てていきます。

アプリ全体の設計をイメージする

どんなStateが必要かを先に考える

まず、「どんな情報をStateとして持つか」を整理します。

TODOアプリ Ver.1 では、最低限次のStateが必要になります。

  • 入力中のタスクタイトル(テキストボックスの中身)
  • タスク一覧(配列)
    • 各タスクはオブジェクトで管理
    • idtitledone を持たせる

イメージとしては、次のような構造です。

const task = {
  id: 1,
  title: "Reactの勉強",
  done: false,
};
JavaScript

これを配列として持ちます。

const tasks = [
  { id: 1, title: "Reactの勉強", done: false },
  { id: 2, title: "買い物に行く", done: true },
];
JavaScript

Reactコンポーネントでは、次のようにStateを宣言します。

import { useState } from "react";

function TodoAppV1() {
  const [title, setTitle] = useState("");
  const [tasks, setTasks] = useState([]);
  const [nextId, setNextId] = useState(1);

  return (
    <div>
      <h2>TODOアプリ Ver.1(State総合制作 前半)</h2>
      {/* ここにUIを作っていきます */}
    </div>
  );
}

export default TodoAppV1;
JSX

ここでのポイントは、

  • title:入力欄の中身
  • tasks:タスク一覧(配列)
  • nextId:新しいタスクに割り当てるID

という役割がはっきり分かれていることです。

タスク追加機能を作る

入力欄と「追加」ボタンを用意する

まずは、タスクを追加するためのUIを作ります。

function TodoAppV1() {
  const [title, setTitle] = useState("");
  const [tasks, setTasks] = useState([]);
  const [nextId, setNextId] = useState(1);

  const handleAddTask = () => {
    const trimmed = title.trim();
    if (trimmed === "") return;

    const newTask = {
      id: nextId,
      title: trimmed,
      done: false,
    };

    setTasks((prevTasks) => [...prevTasks, newTask]);
    setNextId((prevId) => prevId + 1);
    setTitle("");
  };

  return (
    <div>
      <h2>TODOアプリ Ver.1(State総合制作 前半)</h2>

      <div>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="タスクを入力してください"
        />
        <button onClick={handleAddTask}>追加</button>
      </div>
    </div>
  );
}
JSX

ここで起きていることを分解して説明します。

入力欄とStateのつながり

  • value={title} → 入力欄の中身は、常にState title の値になります。
  • onChange={(e) => setTitle(e.target.value)} → ユーザーが入力するたびに、setTitle でStateを更新します。

これにより、入力欄は「Reactに支配された状態」になります。

タスク追加の流れ

handleAddTask の中では、次のステップで処理しています。

  1. title.trim() で前後の空白を削除する
  2. 空文字なら何もしない(バリデーション)
  3. newTask オブジェクトを作る
    • id: nextId を使う
    • title: 入力された文字列
    • done: 初期値は false
  4. setTasks((prevTasks) => [...prevTasks, newTask])
    • 以前のタスク一覧 prevTasksnewTask を末尾追加した新しい配列を作る
    • Spread構文で「元の配列を壊さず、新しい配列を作る」
  5. setNextId((prevId) => prevId + 1) で次のIDを準備する
  6. setTitle("") で入力欄を空に戻す

重要ポイント:

追加処理では、

  • 新しいオブジェクトを作る
  • Spread構文で新しい配列を作る という「不変性」を守る形が基本になります。

タスク一覧表示を作る

map()で配列を画面に変換する

次に、tasks を画面に一覧表示します。

return (
  <div>
    <h2>TODOアプリ Ver.1(State総合制作 前半)</h2>

    <div>
      <input
        type="text"
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="タスクを入力してください"
      />
      <button onClick={handleAddTask}>追加</button>
    </div>

    <ul style={{ marginTop: "8px" }}>
      {tasks.map((task) => (
        <li key={task.id}>
          {task.title}
        </li>
      ))}
    </ul>
  </div>
);
JSX

ここでのポイントは、

  • tasks.map((task) => (...)) → 配列 tasks の各要素 task を、<li>...</li> に変換しています。
  • key={task.id} → Reactが要素を識別するためのキーとして id を使っています。

重要ポイント:

map() は「配列を画面用の要素に変換する」ための基本ツールです。 TODOアプリでは、一覧表示で必ず登場します。

完了機能を作る(状態の切り替え)

完了ボタンと完了状態の表示

次に、「完了状態の切り替え」を実装します。

const handleToggleDone = (id) => {
  setTasks((prevTasks) =>
    prevTasks.map((task) =>
      task.id === id ? { ...task, done: !task.done } : task
    )
  );
};
JSX

表示側はこうなります。

<ul style={{ marginTop: "8px" }}>
  {tasks.map((task) => (
    <li key={task.id}>
      <button onClick={() => handleToggleDone(task.id)}>
        {task.done ? "✅" : "⬜️"}
      </button>
      <span
        style={{
          marginLeft: "8px",
          textDecoration: task.done ? "line-through" : "none",
        }}
      >
        {task.title}
      </span>
    </li>
  ))}
</ul>
JSX

完了状態切り替えの中身

handleToggleDone の中では、次のことをしています。

  1. setTasks((prevTasks) => prevTasks.map(...)) → 以前のタスク一覧を map() で変換し、新しい配列を作る
  2. task.id === id で「今回完了状態を変えたいタスクかどうか」を判定する
  3. 対象なら { ...task, done: !task.done } → Spread構文で task をコピーしつつ、done だけを反転して上書きした新しいオブジェクトを返す
  4. 対象でなければ task をそのまま返す

結果として、

「対象のタスクだけ done が反転した新しい配列」

がStateとして保存されます。

ここが、DAY 28で学んだ「一部更新+完了状態変更」の総復習になっています。

削除機能を作る

削除ボタンとfilter()によるState更新

最後に、「削除」機能を追加します。

const handleDeleteTask = (id) => {
  setTasks((prevTasks) =>
    prevTasks.filter((task) => task.id !== id)
  );
};
JSX

表示側に削除ボタンを追加します。

<ul style={{ marginTop: "8px" }}>
  {tasks.map((task) => (
    <li key={task.id}>
      <button onClick={() => handleToggleDone(task.id)}>
        {task.done ? "✅" : "⬜️"}
      </button>
      <span
        style={{
          marginLeft: "8px",
          textDecoration: task.done ? "line-through" : "none",
        }}
      >
        {task.title}
      </span>
      <button
        style={{ marginLeft: "8px" }}
        onClick={() => handleDeleteTask(task.id)}
      >
        削除
      </button>
    </li>
  ))}
</ul>
JSX

削除処理の中身

handleDeleteTask の中では、次のことをしています。

  1. setTasks((prevTasks) => prevTasks.filter(...)) → 以前のタスク一覧から、条件に合うものだけを残した新しい配列を作る
  2. task.id !== id → 「削除したいIDと一致しないタスクだけを残す」という条件
  3. 結果として、「指定したIDのタスクが取り除かれた新しい配列」がStateとして保存される

重要ポイント:

削除は「残したいものだけを残す」という発想で書きます。 filter() は「条件に合う要素だけを残した新しい配列」を作るためのメソッドです。

TODOアプリ Ver.1(前半完成版テンプレート)

ここまでの内容をすべて含んだ、 TODOアプリ Ver.1(前半)の完成コードをまとめます。

import { useState } from "react";

function TodoAppV1() {
  const [title, setTitle] = useState("");
  const [tasks, setTasks] = useState([]);
  const [nextId, setNextId] = useState(1);

  const handleAddTask = () => {
    const trimmed = title.trim();
    if (trimmed === "") return;

    const newTask = {
      id: nextId,
      title: trimmed,
      done: false,
    };

    setTasks((prevTasks) => [...prevTasks, newTask]);
    setNextId((prevId) => prevId + 1);
    setTitle("");
  };

  const handleToggleDone = (id) => {
    setTasks((prevTasks) =>
      prevTasks.map((task) =>
        task.id === id ? { ...task, done: !task.done } : task
      )
    );
  };

  const handleDeleteTask = (id) => {
    setTasks((prevTasks) =>
      prevTasks.filter((task) => task.id !== id)
    );
  };

  return (
    <div>
      <h2>TODOアプリ Ver.1(State総合制作 前半)</h2>

      <div>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="タスクを入力してください"
        />
        <button onClick={handleAddTask}>追加</button>
      </div>

      <ul style={{ marginTop: "8px" }}>
        {tasks.map((task) => (
          <li key={task.id}>
            <button onClick={() => handleToggleDone(task.id)}>
              {task.done ? "✅" : "⬜️"}
            </button>
            <span
              style={{
                marginLeft: "8px",
                textDecoration: task.done ? "line-through" : "none",
              }}
            >
              {task.title}
            </span>
            <button
              style={{ marginLeft: "8px" }}
              onClick={() => handleDeleteTask(task.id)}
            >
              削除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoAppV1;
JSX

DAY 30 前半のまとめ

DAY 30前半で復習・定着してほしいのは、次のポイントです。

  • タスク追加:入力値をStateで管理し、オブジェクトを作って配列StateにSpread構文で追加すること
  • タスク一覧tasks.map(...) で配列を画面用の要素に変換し、keyid を使うこと
  • 完了状態の切り替えmap()id+Spread構文で「対象だけ一部更新した新しい配列」を作ること
  • 削除filter()id で「残したいものだけを残した新しい配列」を作ること

このTODOアプリ Ver.1 は、 Reactで何度も登場する「基本パターンの集合」です。

ここまでの流れが自分の手で書けるようになると、 この先の機能追加(フィルタリング・編集・優先度など)も、 同じ考え方で自然に組み立てられるようになっていきます。


DAY 30:State総合制作 中盤 ― 「動くTODOアプリ」を設計目線で理解する

DAY 30の中盤では、前半で作った TODOアプリ Ver.1 を題材に、 ただ「動くコード」を書くだけでなく、 なぜその設計なのか/どこが重要なポイントなのか を意識しながら整理していきます。

機能は変わらずシンプルです。

  • タスク追加
  • タスク一覧
  • 完了
  • 削除

ですが、中盤ではこれを「ReactのState設計の基本パターン」として、 一段深く理解していくことを目標にします。

TODOアプリ Ver.1のState設計をもう一度見直す

どんなStateを持っているかを言語化する

前半で書いたStateは、次の3つでした。

const [title, setTitle] = useState("");
const [tasks, setTasks] = useState([]);
const [nextId, setNextId] = useState(1);
JSX

それぞれの役割を、改めて言葉にしてみます。

  • title入力中のタスクタイトル。 テキストボックスの中身を表す、一時的な文字列です。
  • tasksタスク一覧。 各タスクはオブジェクトで、idtitledone を持ちます。jsconst task = { id: 1, title: "Reactの勉強", done: false, };
  • nextId次に追加するタスクに割り当てるID。 タスクを一意に識別するための番号です。

ここで重要なのは、

「画面に必要な情報」を、 それぞれ意味のある単位でStateとして持っている

ということです。

  • 入力欄の中身は title
  • 一覧は tasks
  • ID管理は nextId

このように役割が分かれていると、 後から機能を追加するときも迷いにくくなります。

タスク追加を「安全なパターン」として固定する

追加処理のステップを言葉で追う

前半で書いた handleAddTask をもう一度見てみます。

const handleAddTask = () => {
  const trimmed = title.trim();
  if (trimmed === "") return;

  const newTask = {
    id: nextId,
    title: trimmed,
    done: false,
  };

  setTasks((prevTasks) => [...prevTasks, newTask]);
  setNextId((prevId) => prevId + 1);
  setTitle("");
};
JSX

ここでやっていることを、ステップで整理します。

  1. 入力値の整形とバリデーションjsxconst trimmed = title.trim(); if (trimmed === "") return;
    • 前後の空白を削除してから使う
    • 空文字なら追加しない(無意味なタスクを防ぐ)
  2. 新しいタスクオブジェクトの作成jsxconst newTask = { id: nextId, title: trimmed, done: false, };
    • id:一意な識別子
    • title:ユーザーが入力した文字列
    • done:初期値は false(未完了)
  3. 配列Stateへの追加(不変性を守る)jsxsetTasks((prevTasks) => [...prevTasks, newTask]);
    • prevTasks をSpread構文でコピーしつつ、末尾に newTask を追加
    • 「元の配列を直接書き換えず、新しい配列を作る」形になっている
  4. IDの更新と入力欄のリセットjsxsetNextId((prevId) => prevId + 1); setTitle("");
    • 次のタスク用にIDを1つ進める
    • 入力欄を空に戻して、次の入力に備える

ここで中盤として意識したいこと:

追加処理は、

  • 新しいオブジェクトを作る
  • Spread構文で新しい配列を作る という「不変性を守るパターン」の代表例である。

このパターンは、 「コメント追加」「メッセージ追加」「商品追加」など、 あらゆる「一覧への追加」でそのまま使えます。

タスク一覧表示を「mapの基本パターン」として理解する

mapで「データ → 画面」を変換する

一覧表示部分のコードはこうでした。

<ul style={{ marginTop: "8px" }}>
  {tasks.map((task) => (
    <li key={task.id}>
      {task.title}
    </li>
  ))}
</ul>
JSX

ここで起きていることは、

  • tasks(配列)を
  • task(各要素)ごとに li 要素に変換して
  • 新しい配列(JSXの配列)を作っている

という流れです。

重要なポイント:

  • key={task.id} → Reactが「どの要素がどれか」を識別するためのキー。 IDを使うことで、並び替えや削除が入っても安定します。
  • tasks.map(...) → 「配列を画面用の要素に変換する」ための標準パターン。

中盤では、これを単なる「書き方」ではなく、

「データを画面に映すための変換処理」

として意識してみてください。

完了機能を「一部更新の標準パターン」として定着させる

完了状態の切り替えの中身を丁寧に読む

完了機能のコードはこうでした。

const handleToggleDone = (id) => {
  setTasks((prevTasks) =>
    prevTasks.map((task) =>
      task.id === id ? { ...task, done: !task.done } : task
    )
  );
};
JSX

ここでの流れを、もう一度丁寧に追います。

  1. 以前のStateを受け取るjsxsetTasks((prevTasks) => ...)
    • prevTasks は「更新前のタスク一覧」です。
  2. mapで「一部だけ変えた新しい配列」を作るjsxprevTasks.map((task) => task.id === id ? { ...task, done: !task.done } : task )
    • task について、 「今回の対象かどうか」を task.id === id で判定します。
  3. 対象ならSpread構文で一部更新jsx{ ...task, done: !task.done }
    • task をコピーしつつ、done だけを反転して上書きした新しいオブジェクトを作ります。
  4. 対象でなければそのまま返すjsx: task
    • 他のタスクは一切変更せず、そのまま返します。

結果として、

「対象のタスクだけ done が反転した新しい配列」

がStateとして保存されます。

ここで中盤として固めたいこと:

一部更新の標準形は、 mapid+Spread構文。 「対象だけ変えて、他はそのまま」というメリハリが重要。

このパターンは、

  • いいね/いいね解除
  • フォロー/フォロー解除
  • ON/OFFスイッチ
  • フラグの切り替え

など、あらゆる「一部だけ変える」操作でそのまま使えます。

削除機能を「filterの基本パターン」として理解する

削除は「残したいものだけを残す」発想

削除機能のコードはこうでした。

const handleDeleteTask = (id) => {
  setTasks((prevTasks) =>
    prevTasks.filter((task) => task.id !== id)
  );
};
JSX

ここでの流れは、

  1. 以前のStateを受け取るjsxsetTasks((prevTasks) => ...)
  2. filterで「残したいものだけを残した新しい配列」を作るjsxprevTasks.filter((task) => task.id !== id)
    • task.id !== id → 「削除したいIDと一致しないタスクだけを残す」という条件です。

結果として、

「指定したIDのタスクが取り除かれた新しい配列」

がStateとして保存されます。

ここで中盤として意識したいこと:

削除は「消したいものを指定する」のではなく、 「残したいものだけを残す」発想で書く。 filter() はそのための道具。

このパターンは、

  • コメント削除
  • メッセージ削除
  • 商品削除
  • 通知削除

など、あらゆる「一覧から1件を消す」操作で使えます。

TODOアプリ Ver.1 中盤版テンプレート(設計目線でコメント付き)

最後に、中盤の視点を踏まえたテンプレートをまとめます。

import { useState } from "react";

function TodoAppV1() {
  // 入力中のタイトル
  const [title, setTitle] = useState("");
  // タスク一覧(配列)
  const [tasks, setTasks] = useState([]);
  // 次に使うID
  const [nextId, setNextId] = useState(1);

  // タスク追加
  const handleAddTask = () => {
    const trimmed = title.trim();
    if (trimmed === "") return; // 空は追加しない

    const newTask = {
      id: nextId,
      title: trimmed,
      done: false,
    };

    // 以前の配列を壊さず、新しい配列を作る
    setTasks((prevTasks) => [...prevTasks, newTask]);
    setNextId((prevId) => prevId + 1);
    setTitle(""); // 入力欄をリセット
  };

  // 完了状態の切り替え(map+id+Spread)
  const handleToggleDone = (id) => {
    setTasks((prevTasks) =>
      prevTasks.map((task) =>
        task.id === id ? { ...task, done: !task.done } : task
      )
    );
  };

  // 削除(filter+id)
  const handleDeleteTask = (id) => {
    setTasks((prevTasks) =>
      prevTasks.filter((task) => task.id !== id)
    );
  };

  return (
    <div>
      <h2>TODOアプリ Ver.1(State総合制作 中盤)</h2>

      {/* タスク追加エリア */}
      <div>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="タスクを入力してください"
        />
        <button onClick={handleAddTask}>追加</button>
      </div>

      {/* タスク一覧表示 */}
      <ul style={{ marginTop: "8px" }}>
        {tasks.map((task) => (
          <li key={task.id}>
            {/* 完了ボタン */}
            <button onClick={() => handleToggleDone(task.id)}>
              {task.done ? "✅" : "⬜️"}
            </button>

            {/* タイトル表示(完了なら取り消し線) */}
            <span
              style={{
                marginLeft: "8px",
                textDecoration: task.done ? "line-through" : "none",
              }}
            >
              {task.title}
            </span>

            {/* 削除ボタン */}
            <button
              style={{ marginLeft: "8px" }}
              onClick={() => handleDeleteTask(task.id)}
            >
              削除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoAppV1;
JSX

DAY 30 中盤のまとめ ― 「TODO Ver.1」を自分の基本パターンにする

DAY 30中盤で、自分の中に定着させてほしいのは次の4つです。

  • タスク追加: 新しいオブジェクトを作り、Spread構文で新しい配列を作る「不変性を守る追加」の形。
  • タスク一覧tasks.map(...) で「データ → 画面」を変換し、keyid を使う基本パターン。
  • 完了mapid+Spread構文で「対象だけ一部更新した新しい配列」を作る一部更新の標準形。
  • 削除filterid で「残したいものだけを残した新しい配列」を作る削除の標準形。

このTODOアプリ Ver.1は、 Reactで何度も再利用される「基本の型」です。

ここまでの流れが「なんとなく」ではなく、 「こういう理由でこの形になっている」と説明できる状態になると、 この先の機能追加や別アプリの制作でも、 迷わずState設計ができるようになっていきます。


DAY 30:State総合制作(後半)― TODOアプリ Ver.1 を「自分の定番パターン」に仕上げる

DAY 30の後半では、 これまで作ってきた TODOアプリ Ver.1(タスク追加・一覧・完了・削除) を、 「なんとなく動くコード」から 「いつでも再利用できる自分の定番パターン」 に仕上げていきます。

ここまでで一通り動くものはできていますので、 後半では次の視点で深掘りしていきます。

  • Stateの役割をはっきり言語化する
  • それぞれの機能を「パターン」として整理する
  • コードを少しだけ整えて、読みやすさと拡張しやすさを上げる

アプリ全体のStateをもう一度整理する

3つのStateが何を表しているかを明確にする

TODOアプリ Ver.1 では、次の3つのStateを使っています。

const [title, setTitle] = useState("");
const [tasks, setTasks] = useState([]);
const [nextId, setNextId] = useState(1);
JSX

それぞれの意味を、後半のタイミングで改めて整理しておきます。

  • title
    • 入力中のタスクタイトルです。
    • テキストボックスの中身を表す、一時的な文字列です。
    • 「ユーザーが今何を書いているか」を表すUIの状態です。
  • tasks
    • タスク一覧です。
    • 各タスクはオブジェクトで、idtitledone を持ちます。jsconst task = { id: 1, title: "Reactの勉強", done: false, };
    • 「アプリが管理している本体のデータ」です。
  • nextId
    • 次に追加するタスクに割り当てるIDです。
    • タスクを一意に識別するための番号です。
    • 「新しいタスクを作るためのカウンター」です。

ここで定着させたい感覚:

Stateには「UIの状態」と「アプリの本体データ」の両方がある。 TODOアプリでは、title がUI側、tasks が本体側の代表例になっている。

この視点があると、 今後もっと複雑な画面を作るときにも、 「これはUIの状態か?それとも本体データか?」と考えながらStateを設計できるようになります。

タスク追加機能を「再利用可能な型」として固める

追加処理の流れをパターンとして覚える

タスク追加の処理は次のようになっていました。

const handleAddTask = () => {
  const trimmed = title.trim();
  if (trimmed === "") return;

  const newTask = {
    id: nextId,
    title: trimmed,
    done: false,
  };

  setTasks((prevTasks) => [...prevTasks, newTask]);
  setNextId((prevId) => prevId + 1);
  setTitle("");
};
JSX

この処理を「パターン」として整理すると、次のようになります。

  1. 入力値の整形とバリデーション
    • trim() で前後の空白を削除する
    • 空文字なら何もしない
    これは、どんなフォームでもほぼ必ず必要になる「入り口のチェック」です。
  2. 新しいオブジェクトの作成
    • idtitledone を持つ newTask を作る
    ここでは、「1件分のデータ」をオブジェクトとして表現しています。
  3. 配列Stateへの追加(不変性を守る)jsxsetTasks((prevTasks) => [...prevTasks, newTask]);
    • 以前の配列 prevTasks をSpread構文でコピーしつつ、末尾に newTask を追加
    • 「元の配列を直接書き換えず、新しい配列を作る」形になっています。
  4. IDの更新と入力欄のリセット
    • setNextId((prevId) => prevId + 1)
    • setTitle("")
    これにより、「次の追加」と「次の入力」に備えます。

この流れは、そのまま「一覧への追加の基本パターン」として再利用できます。

例えば、

  • コメント追加
  • メッセージ追加
  • 商品追加
  • ノート追加

など、どんな「追加」機能でも、

入力値チェック → 新しいオブジェクト作成 → Spreadで配列に追加

という同じ型で書けます。

タスク一覧表示を「map+keyの基本形」として固定する

mapで「データ → JSX」に変換する

一覧表示部分は次のようになっています。

<ul style={{ marginTop: "8px" }}>
  {tasks.map((task) => (
    <li key={task.id}>
      {/* 完了ボタン・タイトル・削除ボタン */}
    </li>
  ))}
</ul>
JSX

ここで起きていることは、

  • tasks(配列)を
  • task(各要素)ごとに li 要素に変換して
  • 新しい配列(JSXの配列)を作っている

という流れです。

重要なポイントは2つです。

  • map() を使って「配列 → JSX」に変換していること
  • key={task.id} で、Reactが要素を識別するためのキーを渡していること

keyid を使うのは、 削除や並び替えが入ってもReactが要素を正しく追跡できるようにするためです。

この形は、どんな一覧でもほぼそのまま使えます。

items.map((item) => <li key={item.id}>...</li>)

という形を、「一覧表示の基本形」として覚えておくと、 Reactの画面作りがぐっと楽になります。

完了機能を「一部更新の代表パターン」として仕上げる

完了状態切り替えのコードを、もう一度じっくり読む

完了機能のコードは次のようになっています。

const handleToggleDone = (id) => {
  setTasks((prevTasks) =>
    prevTasks.map((task) =>
      task.id === id ? { ...task, done: !task.done } : task
    )
  );
};
JSX

ここでの処理を、後半のタイミングで改めて整理します。

  1. 以前のStateを受け取るjsxsetTasks((prevTasks) => ...)
    • prevTasks は「更新前のタスク一覧」です。
  2. mapで「一部だけ変えた新しい配列」を作るjsxprevTasks.map((task) => task.id === id ? { ...task, done: !task.done } : task )
    • task について、 「今回の対象かどうか」を task.id === id で判定します。
  3. 対象ならSpread構文で一部更新jsx{ ...task, done: !task.done }
    • task をコピーしつつ、done だけを反転して上書きした新しいオブジェクトを作ります。
  4. 対象でなければそのまま返すjsx: task
    • 他のタスクは一切変更せず、そのまま返します。

結果として、

「対象のタスクだけ done が反転した新しい配列」

がStateとして保存されます。

ここで定着させたい「型」はこれです。

setItems((prevItems) =>
  prevItems.map((item) =>
    item.id === id ? { ...item, 変えたいプロパティ: 新しい値 } : item
  )
);
JSX

この型は、

  • いいね/いいね解除
  • フォロー/フォロー解除
  • ON/OFFスイッチ
  • フラグの切り替え

など、あらゆる「一部だけ変える」操作でそのまま使えます。

削除機能を「filterの標準パターン」として固める

削除処理を「残したいものだけを残す」視点で理解する

削除機能のコードは次のようになっています。

const handleDeleteTask = (id) => {
  setTasks((prevTasks) =>
    prevTasks.filter((task) => task.id !== id)
  );
};
JSX

ここでの処理はとてもシンプルですが、 考え方として非常に重要です。

  1. 以前のStateを受け取るjsxsetTasks((prevTasks) => ...)
  2. filterで「残したいものだけを残した新しい配列」を作るjsxprevTasks.filter((task) => task.id !== id)
    • task.id !== id → 「削除したいIDと一致しないタスクだけを残す」という条件です。

結果として、

「指定したIDのタスクが取り除かれた新しい配列」

がStateとして保存されます。

ここで覚えておきたい「型」はこれです。

setItems((prevItems) =>
  prevItems.filter((item) => item.id !== id)
);
JSX

この型は、

  • コメント削除
  • メッセージ削除
  • 商品削除
  • 通知削除

など、あらゆる「一覧から1件を消す」操作で使えます。

TODOアプリ Ver.1(後半版)― 少しだけ整えた完成テンプレート

最後に、DAY 30後半の視点を踏まえた TODOアプリ Ver.1 の完成コードをまとめます。

import { useState } from "react";

function TodoAppV1() {
  // 入力中のタイトル(UIの状態)
  const [title, setTitle] = useState("");
  // タスク一覧(アプリの本体データ)
  const [tasks, setTasks] = useState([]);
  // 次に使うID(識別子のカウンター)
  const [nextId, setNextId] = useState(1);

  // タスク追加
  const handleAddTask = () => {
    const trimmed = title.trim();
    if (trimmed === "") return; // 空は追加しない

    const newTask = {
      id: nextId,
      title: trimmed,
      done: false,
    };

    // 以前の配列を壊さず、新しい配列を作る
    setTasks((prevTasks) => [...prevTasks, newTask]);
    setNextId((prevId) => prevId + 1);
    setTitle(""); // 入力欄をリセット
  };

  // 完了状態の切り替え(map+id+Spread)
  const handleToggleDone = (id) => {
    setTasks((prevTasks) =>
      prevTasks.map((task) =>
        task.id === id ? { ...task, done: !task.done } : task
      )
    );
  };

  // 削除(filter+id)
  const handleDeleteTask = (id) => {
    setTasks((prevTasks) =>
      prevTasks.filter((task) => task.id !== id)
    );
  };

  return (
    <div>
      <h2>TODOアプリ Ver.1(State総合制作 後半)</h2>

      {/* タスク追加エリア */}
      <div>
        <input
          type="text"
          value={title}
          onChange={(e) => setTitle(e.target.value)}
          placeholder="タスクを入力してください"
        />
        <button onClick={handleAddTask}>追加</button>
      </div>

      {/* タスク一覧表示 */}
      <ul style={{ marginTop: "8px" }}>
        {tasks.map((task) => (
          <li key={task.id}>
            {/* 完了ボタン */}
            <button onClick={() => handleToggleDone(task.id)}>
              {task.done ? "✅" : "⬜️"}
            </button>

            {/* タイトル表示(完了なら取り消し線) */}
            <span
              style={{
                marginLeft: "8px",
                textDecoration: task.done ? "line-through" : "none",
              }}
            >
              {task.title}
            </span>

            {/* 削除ボタン */}
            <button
              style={{ marginLeft: "8px" }}
              onClick={() => handleDeleteTask(task.id)}
            >
              削除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoAppV1;
JSX

DAY 30 後半のまとめ ― 「TODO Ver.1」を自分の基本パターンとして持つ

DAY 30後半で、自分の中にしっかり残してほしいのは次の4つです。

  • タスク追加: 入力値チェック → 新しいオブジェクト作成 → Spreadで配列に追加、という「追加の基本パターン」。
  • タスク一覧items.map(...)key={id} という「一覧表示の基本パターン」。
  • 完了(状態の切り替え)mapid+Spread構文で「対象だけ一部更新した新しい配列」を作る一部更新の標準形。
  • 削除filterid で「残したいものだけを残した新しい配列」を作る削除の標準形。

この TODOアプリ Ver.1 は、 Reactで何度も再利用される「State管理の基本セット」です。

ここまでの流れが自分の中で「型」として固まると、 別のアプリを作るときにも、

「あ、これはTODO Ver.1のパターンで書けるな」

と自然に思えるようになり、 Reactでの開発が一気に楽になっていきます。

タイトルとURLをコピーしました