- DAY 30:State総合制作 前半 ― TODOアプリ Ver.1 を作りながら復習する
- アプリ全体の設計をイメージする
- タスク追加機能を作る
- タスク一覧表示を作る
- 完了機能を作る(状態の切り替え)
- 削除機能を作る
- TODOアプリ Ver.1(前半完成版テンプレート)
- DAY 30 前半のまとめ
- DAY 30:State総合制作 中盤 ― 「動くTODOアプリ」を設計目線で理解する
- TODOアプリ Ver.1のState設計をもう一度見直す
- タスク追加を「安全なパターン」として固定する
- タスク一覧表示を「mapの基本パターン」として理解する
- 完了機能を「一部更新の標準パターン」として定着させる
- 削除機能を「filterの基本パターン」として理解する
- TODOアプリ Ver.1 中盤版テンプレート(設計目線でコメント付き)
- DAY 30 中盤のまとめ ― 「TODO Ver.1」を自分の基本パターンにする
- DAY 30:State総合制作(後半)― TODOアプリ Ver.1 を「自分の定番パターン」に仕上げる
- アプリ全体のStateをもう一度整理する
- タスク追加機能を「再利用可能な型」として固める
- タスク一覧表示を「map+keyの基本形」として固定する
- 完了機能を「一部更新の代表パターン」として仕上げる
- 削除機能を「filterの標準パターン」として固める
- TODOアプリ Ver.1(後半版)― 少しだけ整えた完成テンプレート
- 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が必要になります。
- 入力中のタスクタイトル(テキストボックスの中身)
- タスク一覧(配列)
- 各タスクはオブジェクトで管理
id・title・doneを持たせる
イメージとしては、次のような構造です。
const task = {
id: 1,
title: "Reactの勉強",
done: false,
};
JavaScriptこれを配列として持ちます。
const tasks = [
{ id: 1, title: "Reactの勉強", done: false },
{ id: 2, title: "買い物に行く", done: true },
];
JavaScriptReactコンポーネントでは、次のように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}→ 入力欄の中身は、常にStatetitleの値になります。onChange={(e) => setTitle(e.target.value)}→ ユーザーが入力するたびに、setTitleでStateを更新します。
これにより、入力欄は「Reactに支配された状態」になります。
タスク追加の流れ
handleAddTask の中では、次のステップで処理しています。
title.trim()で前後の空白を削除する- 空文字なら何もしない(バリデーション)
newTaskオブジェクトを作るid:nextIdを使うtitle: 入力された文字列done: 初期値はfalse
setTasks((prevTasks) => [...prevTasks, newTask])- 以前のタスク一覧
prevTasksにnewTaskを末尾追加した新しい配列を作る - Spread構文で「元の配列を壊さず、新しい配列を作る」
- 以前のタスク一覧
setNextId((prevId) => prevId + 1)で次のIDを準備する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 の中では、次のことをしています。
setTasks((prevTasks) => prevTasks.map(...))→ 以前のタスク一覧をmap()で変換し、新しい配列を作るtask.id === idで「今回完了状態を変えたいタスクかどうか」を判定する- 対象なら
{ ...task, done: !task.done }→ Spread構文でtaskをコピーしつつ、doneだけを反転して上書きした新しいオブジェクトを返す - 対象でなければ
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 の中では、次のことをしています。
setTasks((prevTasks) => prevTasks.filter(...))→ 以前のタスク一覧から、条件に合うものだけを残した新しい配列を作るtask.id !== id→ 「削除したいIDと一致しないタスクだけを残す」という条件- 結果として、「指定した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;
JSXDAY 30 前半のまとめ
DAY 30前半で復習・定着してほしいのは、次のポイントです。
- タスク追加:入力値をStateで管理し、オブジェクトを作って配列StateにSpread構文で追加すること
- タスク一覧:
tasks.map(...)で配列を画面用の要素に変換し、keyにidを使うこと - 完了状態の切り替え:
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: タスク一覧。 各タスクはオブジェクトで、id・title・doneを持ちます。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ここでやっていることを、ステップで整理します。
- 入力値の整形とバリデーションjsx
const trimmed = title.trim(); if (trimmed === "") return;- 前後の空白を削除してから使う
- 空文字なら追加しない(無意味なタスクを防ぐ)
- 新しいタスクオブジェクトの作成jsx
const newTask = { id: nextId, title: trimmed, done: false, };id:一意な識別子title:ユーザーが入力した文字列done:初期値はfalse(未完了)
- 配列Stateへの追加(不変性を守る)jsx
setTasks((prevTasks) => [...prevTasks, newTask]);prevTasksをSpread構文でコピーしつつ、末尾にnewTaskを追加- 「元の配列を直接書き換えず、新しい配列を作る」形になっている
- IDの更新と入力欄のリセットjsx
setNextId((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ここでの流れを、もう一度丁寧に追います。
- 以前のStateを受け取るjsx
setTasks((prevTasks) => ...)prevTasksは「更新前のタスク一覧」です。
- mapで「一部だけ変えた新しい配列」を作るjsx
prevTasks.map((task) => task.id === id ? { ...task, done: !task.done } : task )- 各
taskについて、 「今回の対象かどうか」をtask.id === idで判定します。
- 各
- 対象ならSpread構文で一部更新jsx
{ ...task, done: !task.done }taskをコピーしつつ、doneだけを反転して上書きした新しいオブジェクトを作ります。
- 対象でなければそのまま返すjsx
: task- 他のタスクは一切変更せず、そのまま返します。
結果として、
「対象のタスクだけ
doneが反転した新しい配列」
がStateとして保存されます。
ここで中盤として固めたいこと:
一部更新の標準形は、
map+id+Spread構文。 「対象だけ変えて、他はそのまま」というメリハリが重要。
このパターンは、
- いいね/いいね解除
- フォロー/フォロー解除
- ON/OFFスイッチ
- フラグの切り替え
など、あらゆる「一部だけ変える」操作でそのまま使えます。
削除機能を「filterの基本パターン」として理解する
削除は「残したいものだけを残す」発想
削除機能のコードはこうでした。
const handleDeleteTask = (id) => {
setTasks((prevTasks) =>
prevTasks.filter((task) => task.id !== id)
);
};
JSXここでの流れは、
- 以前のStateを受け取るjsx
setTasks((prevTasks) => ...) - filterで「残したいものだけを残した新しい配列」を作るjsx
prevTasks.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;
JSXDAY 30 中盤のまとめ ― 「TODO Ver.1」を自分の基本パターンにする
DAY 30中盤で、自分の中に定着させてほしいのは次の4つです。
- タスク追加: 新しいオブジェクトを作り、Spread構文で新しい配列を作る「不変性を守る追加」の形。
- タスク一覧:
tasks.map(...)で「データ → 画面」を変換し、keyにidを使う基本パターン。 - 完了:
map+id+Spread構文で「対象だけ一部更新した新しい配列」を作る一部更新の標準形。 - 削除:
filter+idで「残したいものだけを残した新しい配列」を作る削除の標準形。
この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
- タスク一覧です。
- 各タスクはオブジェクトで、
id・title・doneを持ちます。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この処理を「パターン」として整理すると、次のようになります。
- 入力値の整形とバリデーション
trim()で前後の空白を削除する- 空文字なら何もしない
- 新しいオブジェクトの作成
id・title・doneを持つnewTaskを作る
- 配列Stateへの追加(不変性を守る)jsx
setTasks((prevTasks) => [...prevTasks, newTask]);- 以前の配列
prevTasksをSpread構文でコピーしつつ、末尾にnewTaskを追加 - 「元の配列を直接書き換えず、新しい配列を作る」形になっています。
- 以前の配列
- 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が要素を識別するためのキーを渡していること
key に id を使うのは、 削除や並び替えが入っても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ここでの処理を、後半のタイミングで改めて整理します。
- 以前のStateを受け取るjsx
setTasks((prevTasks) => ...)prevTasksは「更新前のタスク一覧」です。
- mapで「一部だけ変えた新しい配列」を作るjsx
prevTasks.map((task) => task.id === id ? { ...task, done: !task.done } : task )- 各
taskについて、 「今回の対象かどうか」をtask.id === idで判定します。
- 各
- 対象ならSpread構文で一部更新jsx
{ ...task, done: !task.done }taskをコピーしつつ、doneだけを反転して上書きした新しいオブジェクトを作ります。
- 対象でなければそのまま返す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ここでの処理はとてもシンプルですが、 考え方として非常に重要です。
- 以前のStateを受け取るjsx
setTasks((prevTasks) => ...) - filterで「残したいものだけを残した新しい配列」を作るjsx
prevTasks.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;
JSXDAY 30 後半のまとめ ― 「TODO Ver.1」を自分の基本パターンとして持つ
DAY 30後半で、自分の中にしっかり残してほしいのは次の4つです。
- タスク追加: 入力値チェック → 新しいオブジェクト作成 → Spreadで配列に追加、という「追加の基本パターン」。
- タスク一覧:
items.map(...)+key={id}という「一覧表示の基本パターン」。 - 完了(状態の切り替え):
map+id+Spread構文で「対象だけ一部更新した新しい配列」を作る一部更新の標準形。 - 削除:
filter+idで「残したいものだけを残した新しい配列」を作る削除の標準形。
この TODOアプリ Ver.1 は、 Reactで何度も再利用される「State管理の基本セット」です。
ここまでの流れが自分の中で「型」として固まると、 別のアプリを作るときにも、
「あ、これはTODO Ver.1のパターンで書けるな」
と自然に思えるようになり、 Reactでの開発が一気に楽になっていきます。
