- DAY 36:フォーム総合演習 前半 ― 簡易ユーザー登録アプリの全体像をつかむ
- アプリのゴールを言葉で整理する
- コンポーネント構成をシンプルに決める
- ベースコンポーネントを作る(入力+一覧表示の枠)
- 入力バリデーションを組み立てる
- 登録処理を「バリデーションの後」に置く
- 一覧表示と削除機能をつなげる
- DAY 36 前半版テンプレート ― 簡易ユーザー登録アプリ
- DAY 36 前半のまとめ ― 「フォームの一連の流れ」を1つのアプリで体感する
- DAY 36:フォーム総合演習 中盤 ― 「少しだけ実戦寄り」に簡易ユーザー登録アプリを育てる
- アプリの「責務」を言葉で整理してみる
- ユーザーオブジェクトの形をはっきり決める
- バリデーションと登録処理を「読みやすく」する
- 削除処理を「Stateの更新」として理解する
- 一覧表示のUIを少しだけ整える
- DAY 36 中盤版テンプレート ― 少し整理された簡易ユーザー登録アプリ
- DAY 36 中盤のまとめ ― 「流れ+構造」を同時に意識できるようになる
- DAY 36:フォーム総合演習 後半 ― 「実戦を意識した簡易ユーザー登録アプリ」に仕上げる
- アプリ全体の流れをもう一度確認する
- 入力とバリデーションを「ユーザー目線」で整える
- バリデーションを「実戦の入り口」として意識する
- 登録処理と一覧表示を「安全に・分かりやすく」つなぐ
- 削除処理を「State更新の基本パターン」として理解する
- DAY 36 後半版テンプレート ― 実戦寄り簡易ユーザー登録アプリ
- DAY 36 後半のまとめ ― 「フォームの一連の流れ」を自分の手で完結させる
DAY 36:フォーム総合演習 前半 ― 簡易ユーザー登録アプリの全体像をつかむ
DAY 36では、これまで学んできた
- 入力フォーム
- Controlled Component
- フォーム送信
- バリデーション
- 一覧表示
- 削除
をひとつにまとめた「簡易ユーザー登録アプリ」を作っていきます。 前半では、まず全体像をつかみながら、入力〜バリデーション〜登録〜一覧表示〜削除までの流れを、シンプルな形で組み立てていきます。
アプリのゴールを言葉で整理する
どんなことができるアプリなのか
今回作る「簡易ユーザー登録アプリ」は、次のような動きをするものです。
- ユーザーが「名前」と「メール」を入力する
- 入力内容に対してバリデーションを行う
- 名前:必須・最大20文字
- メール:必須・「@」を含む簡易チェック
- バリデーションを通過したら「登録」ボタンでユーザー一覧に追加される
- 画面下部に「登録済みユーザー一覧」が表示される
- 一覧の各ユーザーには「削除」ボタンがあり、クリックするとそのユーザーが一覧から消える
つまり、「フォーム+バリデーション+State管理+一覧表示+削除」という、Reactの基本要素が一通り詰まったミニアプリになります。
コンポーネント構成をシンプルに決める
まずは1コンポーネントで完結させる
前半では、構成をシンプルにするために、 1つのコンポーネントの中にすべてを書いてみる方針で進めます。
コンポーネント名は SimpleUserRegisterApp として、次のようなStateを持ちます。
- 入力用State
name:名前入力欄email:メール入力欄
- バリデーション用State
nameError:名前のエラーメッセージemailError:メールのエラーメッセージ
- 登録済みユーザー一覧
users:[{ id, name, email }, ...]の配列
これをベースに、少しずつ機能を足していきます。
ベースコンポーネントを作る(入力+一覧表示の枠)
まずは「入力欄」と「一覧表示」の枠だけ作る
import { useState } from "react";
function SimpleUserRegisterApp() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [nameError, setNameError] = useState("");
const [emailError, setEmailError] = useState("");
const [users, setUsers] = useState([]);
const handleSubmit = (e) => {
e.preventDefault();
// ここにバリデーション+登録処理を追加していきます
};
return (
<div>
<h2>DAY 36:フォーム総合演習前半 ― 簡易ユーザー登録アプリ</h2>
{/* 入力フォーム */}
<form onSubmit={handleSubmit}>
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
{nameError && (
<p style={{ color: "red", marginTop: "4px" }}>{nameError}</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
{emailError && (
<p style={{ color: "red", marginTop: "4px" }}>{emailError}</p>
)}
</div>
<div style={{ marginTop: "12px" }}>
<button type="submit">登録</button>
</div>
</form>
{/* 一覧表示 */}
<div style={{ marginTop: "24px" }}>
<h3>登録済みユーザー一覧</h3>
{users.length === 0 ? (
<p>まだユーザーは登録されていません。</p>
) : (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name}({user.email})
{/* 削除ボタンは後で追加します */}
</li>
))}
</ul>
)}
</div>
</div>
);
}
export default SimpleUserRegisterApp;
JSXここまでで、
- 入力欄(名前・メール)
- エラーメッセージ表示枠
- 登録済みユーザー一覧の枠
ができました。 次に、バリデーションと登録処理を追加していきます。
入力バリデーションを組み立てる
名前:必須+文字数チェック
まず、名前のバリデーションを関数としてまとめます。
const MAX_NAME_LENGTH = 20;
const validateName = (name) => {
if (name.trim() === "") {
return "名前は必須です。";
}
if (name.length > MAX_NAME_LENGTH) {
return `名前は${MAX_NAME_LENGTH}文字以内で入力してください。`;
}
return "";
};
JSXメール:必須+簡易形式チェック
const validateEmail = (email) => {
if (email.trim() === "") {
return "メールアドレスは必須です。";
}
if (!email.includes("@")) {
return "メールアドレスの形式が正しくありません。";
}
return "";
};
JSXこれらを handleSubmit の中で使います。
登録処理を「バリデーションの後」に置く
handleSubmitの中身をステップで整理する
handleSubmit の中で行うことを順番に整理すると、次のようになります。
preventDefault()でブラウザのデフォルト送信を止める- いったんエラーをクリアする
- 名前・メールのバリデーションを行う
- エラーがあればエラーメッセージをセットして処理を止める
- 問題なければユーザー一覧に追加する
- 入力欄をリセットする
これをコードにすると、次のようになります。
const handleSubmit = (e) => {
e.preventDefault();
setNameError("");
setEmailError("");
const nameValidationResult = validateName(name);
const emailValidationResult = validateEmail(email);
if (nameValidationResult !== "") {
setNameError(nameValidationResult);
}
if (emailValidationResult !== "") {
setEmailError(emailValidationResult);
}
if (nameValidationResult !== "" || emailValidationResult !== "") {
// どちらかにエラーがあれば登録しない
return;
}
// バリデーションを通過したので登録処理へ
const newUser = {
id: Date.now(), // 簡易的なID(実戦ではUUIDなどを使うことが多いです)
name,
email,
};
setUsers((prevUsers) => [...prevUsers, newUser]);
// 入力欄をリセット
setName("");
setEmail("");
};
JSX重要なポイント:
- 「バリデーションに通過したら登録する」という流れを、
handleSubmitの中で明確にしていること setUsersで新しいユーザーを配列に追加していること- 登録後に入力欄を空にして、次の入力に備えていること
一覧表示と削除機能をつなげる
削除ボタンを追加して、ユーザーを取り除く
一覧表示部分に「削除」ボタンを追加し、クリックされたユーザーを一覧から取り除く処理を実装します。
まず、削除用の関数を作ります。
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
JSX一覧表示部分を次のように変更します。
<div style={{ marginTop: "24px" }}>
<h3>登録済みユーザー一覧</h3>
{users.length === 0 ? (
<p>まだユーザーは登録されていません。</p>
) : (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name}({user.email})
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteUser(user.id)}
>
削除
</button>
</li>
))}
</ul>
)}
</div>
JSXこれで、
- 登録されたユーザーが一覧に表示される
- 各ユーザーの横に「削除」ボタンが表示される
- 削除ボタンを押すと、そのユーザーが一覧から消える
という流れが完成します。
DAY 36 前半版テンプレート ― 簡易ユーザー登録アプリ
ここまでの内容をすべて含んだ、 DAY 36前半の「簡易ユーザー登録アプリ」テンプレートをまとめます。
import { useState } from "react";
const MAX_NAME_LENGTH = 20;
function SimpleUserRegisterApp() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [nameError, setNameError] = useState("");
const [emailError, setEmailError] = useState("");
const [users, setUsers] = useState([]);
const validateName = (name) => {
if (name.trim() === "") {
return "名前は必須です。";
}
if (name.length > MAX_NAME_LENGTH) {
return `名前は${MAX_NAME_LENGTH}文字以内で入力してください。`;
}
return "";
};
const validateEmail = (email) => {
if (email.trim() === "") {
return "メールアドレスは必須です。";
}
if (!email.includes("@")) {
return "メールアドレスの形式が正しくありません。";
}
return "";
};
const handleSubmit = (e) => {
e.preventDefault();
setNameError("");
setEmailError("");
const nameValidationResult = validateName(name);
const emailValidationResult = validateEmail(email);
if (nameValidationResult !== "") {
setNameError(nameValidationResult);
}
if (emailValidationResult !== "") {
setEmailError(emailValidationResult);
}
if (nameValidationResult !== "" || emailValidationResult !== "") {
return;
}
const newUser = {
id: Date.now(),
name,
email,
};
setUsers((prevUsers) => [...prevUsers, newUser]);
setName("");
setEmail("");
};
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>DAY 36:フォーム総合演習前半 ― 簡易ユーザー登録アプリ</h2>
<form onSubmit={handleSubmit}>
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
{nameError && (
<p style={{ color: "red", marginTop: "4px" }}>{nameError}</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
{emailError && (
<p style={{ color: "red", marginTop: "4px" }}>{emailError}</p>
)}
</div>
<div style={{ marginTop: "12px" }}>
<button type="submit">登録</button>
</div>
</form>
<div style={{ marginTop: "24px" }}>
<h3>登録済みユーザー一覧</h3>
{users.length === 0 ? (
<p>まだユーザーは登録されていません。</p>
) : (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name}({user.email})
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteUser(user.id)}
>
削除
</button>
</li>
))}
</ul>
)}
</div>
</div>
);
}
export default SimpleUserRegisterApp;
JSXDAY 36 前半のまとめ ― 「フォームの一連の流れ」を1つのアプリで体感する
DAY 36前半で意識してほしいのは次のポイントです。
- 入力 → バリデーション → 登録 → 一覧表示 → 削除 という一連の流れを、1つのコンポーネントの中でつなげられること。
- バリデーションは、送信前に必ず通す「入口のフィルター」として機能していること。
- 登録済みユーザー一覧は、State(配列)として管理し、追加・削除を配列操作で行うこと。
このミニアプリの流れが腑に落ちてくると、 この先の「より複雑なフォームアプリ」も、 同じパターンの拡張として見えてくるようになります。
DAY 36:フォーム総合演習 中盤 ― 「少しだけ実戦寄り」に簡易ユーザー登録アプリを育てる
DAY 36の中盤では、前半で作った
- 入力
- バリデーション
- 登録
- 一覧表示
- 削除
を備えた「簡易ユーザー登録アプリ」を、もう一歩だけ実戦寄りの形に育てることを目指します。
ここでは特に、
- コードの見通しをよくする
- ユーザーオブジェクトの扱い方を整理する
- 削除処理の意味をもう少し深く理解する
という観点で、ステップバイステップでかみ砕いて説明していきます。
アプリの「責務」を言葉で整理してみる
何を管理して、何を表示しているのか
前半で作った簡易ユーザー登録アプリは、ざっくり言うと次の責務を持っています。
- 入力の管理
- 名前・メールの入力値をStateで管理する
- バリデーション
- 名前:必須・最大文字数
- メール:必須・簡易形式チェック
- 登録処理
- バリデーションを通過したユーザーを一覧に追加する
- 一覧表示
- 登録済みユーザーを画面に表示する
- 削除処理
- 一覧から特定のユーザーを取り除く
中盤では、この責務を意識しながら、 コードの構造を少しだけ整理していきます。
ユーザーオブジェクトの形をはっきり決める
「ユーザーとは何か」をコードで表現する
前半では、ユーザーオブジェクトを次のように扱いました。
const newUser = {
id: Date.now(),
name,
email,
};
JSXここでの id は「一意な識別子」として使っています。 中盤では、この「ユーザーの形」をもう少し意識してみます。
id:ユーザーを一意に識別するための値name:表示名email:連絡先・ログインID候補
この3つが揃っていることで、
- 一覧表示で「誰が誰か」を区別できる
- 削除処理で「どのユーザーを消すか」を特定できる
という状態になります。
重要な感覚:
「ユーザーオブジェクトの形」を自分の中で言葉にしておくと、 Stateの配列を扱うときに迷いにくくなります。
バリデーションと登録処理を「読みやすく」する
handleSubmitの中身を整理する
前半の handleSubmit は、すでに機能としては十分ですが、 中盤では「読みやすさ」を意識して少しだけ整理してみます。
const handleSubmit = (e) => {
e.preventDefault();
// 1. エラーをクリア
setNameError("");
setEmailError("");
// 2. バリデーション
const nameValidationResult = validateName(name);
const emailValidationResult = validateEmail(email);
const hasError =
nameValidationResult !== "" || emailValidationResult !== "";
if (nameValidationResult !== "") {
setNameError(nameValidationResult);
}
if (emailValidationResult !== "") {
setEmailError(emailValidationResult);
}
if (hasError) {
// エラーがあるので登録しない
return;
}
// 3. ユーザーオブジェクトの作成
const newUser = {
id: Date.now(),
name,
email,
};
// 4. ユーザー一覧に追加
setUsers((prevUsers) => [...prevUsers, newUser]);
// 5. 入力欄をリセット
setName("");
setEmail("");
};
JSXここでのポイントは、
- 「エラーがあるかどうか」を
hasErrorという変数で表現していること handleSubmitの中身を「1. エラークリア → 2. バリデーション → 3. ユーザー作成 → 4. 追加 → 5. リセット」という流れで整理していること
です。
重要な感覚:
フォーム送信処理は、「何をしているか」が一目で分かるように ステップごとに整理して書くと、後から読み返したときに理解しやすくなります。
削除処理を「Stateの更新」として理解する
filterで「残すものだけ」を選ぶ
削除処理は、次のように書いていました。
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
JSXここで起きていることを言葉で整理すると、
prevUsersは「今のユーザー一覧」filterは「条件を満たす要素だけを残す」関数user.id !== idという条件で、「削除したいユーザー以外」を残している
つまり、
「削除したいユーザーを取り除いた新しい配列」を作り、 それを
setUsersに渡している
ということになります。
重要な感覚:
ReactのStateは「直接書き換えず、新しい値を作って置き換える」というルールがあります。 削除処理も、「新しい配列を作ってStateを更新する」という形で考えると整理しやすくなります。
一覧表示のUIを少しだけ整える
見やすさと操作しやすさを意識する
中盤では、一覧表示のUIを少しだけ整えてみます。
<div style={{ marginTop: "24px" }}>
<h3>登録済みユーザー一覧</h3>
{users.length === 0 ? (
<p>まだユーザーは登録されていません。</p>
) : (
<ul style={{ paddingLeft: "16px" }}>
{users.map((user) => (
<li key={user.id} style={{ marginBottom: "4px" }}>
<span>
{user.name}({user.email})
</span>
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteUser(user.id)}
>
削除
</button>
</li>
))}
</ul>
)}
</div>
JSXここでのポイントは、
marginBottomやpaddingLeftで、一覧の見やすさを少しだけ改善していること- 名前とメールを
spanでまとめて表示していること - 削除ボタンを横に並べて、「どのユーザーを消すか」が直感的に分かるようにしていること
です。
重要な感覚:
UIのちょっとした調整でも、 「ユーザーが迷わず操作できるかどうか」が変わってきます。 コードを書きながら、画面の見え方も意識していくと良いです。
DAY 36 中盤版テンプレート ― 少し整理された簡易ユーザー登録アプリ
ここまでの内容をすべて含んだ、 DAY 36中盤のテンプレートをまとめます。
import { useState } from "react";
const MAX_NAME_LENGTH = 20;
function SimpleUserRegisterAppMid() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [nameError, setNameError] = useState("");
const [emailError, setEmailError] = useState("");
const [users, setUsers] = useState([]);
const validateName = (name) => {
if (name.trim() === "") {
return "名前は必須です。";
}
if (name.length > MAX_NAME_LENGTH) {
return `名前は${MAX_NAME_LENGTH}文字以内で入力してください。`;
}
return "";
};
const validateEmail = (email) => {
if (email.trim() === "") {
return "メールアドレスは必須です。";
}
if (!email.includes("@")) {
return "メールアドレスの形式が正しくありません。";
}
return "";
};
const handleSubmit = (e) => {
e.preventDefault();
setNameError("");
setEmailError("");
const nameValidationResult = validateName(name);
const emailValidationResult = validateEmail(email);
const hasError =
nameValidationResult !== "" || emailValidationResult !== "";
if (nameValidationResult !== "") {
setNameError(nameValidationResult);
}
if (emailValidationResult !== "") {
setEmailError(emailValidationResult);
}
if (hasError) {
return;
}
const newUser = {
id: Date.now(),
name,
email,
};
setUsers((prevUsers) => [...prevUsers, newUser]);
setName("");
setEmail("");
};
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>DAY 36:フォーム総合演習中盤 ― 簡易ユーザー登録アプリ(整理版)</h2>
<form onSubmit={handleSubmit}>
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
{nameError && (
<p style={{ color: "red", marginTop: "4px" }}>{nameError}</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
{emailError && (
<p style={{ color: "red", marginTop: "4px" }}>{emailError}</p>
)}
</div>
<div style={{ marginTop: "12px" }}>
<button type="submit">登録</button>
</div>
</form>
<div style={{ marginTop: "24px" }}>
<h3>登録済みユーザー一覧</h3>
{users.length === 0 ? (
<p>まだユーザーは登録されていません。</p>
) : (
<ul style={{ paddingLeft: "16px" }}>
{users.map((user) => (
<li key={user.id} style={{ marginBottom: "4px" }}>
<span>
{user.name}({user.email})
</span>
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteUser(user.id)}
>
削除
</button>
</li>
))}
</ul>
)}
</div>
</div>
);
}
export default SimpleUserRegisterAppMid;
JSXDAY 36 中盤のまとめ ― 「流れ+構造」を同時に意識できるようになる
DAY 36中盤で定着させてほしいポイントは次の通りです。
- 入力 → バリデーション → 登録 → 一覧表示 → 削除 という流れを、1つのコンポーネントの中で整理して書けること。
- ユーザーオブジェクトの形(
id,name,email)を意識することで、一覧表示や削除処理が理解しやすくなること。 - 削除処理は、「新しい配列を作ってStateを更新する」というReactの基本ルールに沿って考えること。
この「流れ+構造」を同時に意識できる感覚が身につくと、 この先のより大きなアプリでも、 フォームまわりの設計を落ち着いて進められるようになっていきます。
DAY 36:フォーム総合演習 後半 ― 「実戦を意識した簡易ユーザー登録アプリ」に仕上げる
DAY 36の後半では、前半・中盤で作ってきた簡易ユーザー登録アプリを、「実戦を意識した形」に一歩近づけていきます。 すでに、
- 入力
- バリデーション
- 登録
- 一覧表示
- 削除
という一連の流れはできていますので、後半ではそれを「使いやすく・安全に・読みやすく」する方向で整えていきます。
アプリ全体の流れをもう一度確認する
入力から削除までの一連のストーリー
この簡易ユーザー登録アプリは、次のようなストーリーで動きます。
- ユーザーが「名前」と「メール」を入力する(入力)
- 送信ボタンを押すと、バリデーションが走る(バリデーション)
- バリデーションを通過したら、ユーザー一覧に追加される(登録)
- 画面下部に登録済みユーザー一覧が表示される(一覧表示)
- 各ユーザーの横にある「削除」ボタンで、そのユーザーを一覧から消せる(削除)
後半では、この流れを保ちつつ、
- ユーザー体験(UX)
- コードの見通し
- セキュリティの入り口としての意識
を少しだけ強化していきます。
入力とバリデーションを「ユーザー目線」で整える
入力欄にプレースホルダーとラベルを丁寧に付ける
まず、入力欄のUIを少し整えます。 ユーザーが迷わず入力できるように、ラベルとプレースホルダーを丁寧に付けます。
<form onSubmit={handleSubmit}>
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="例:山田太郎"
/>
</label>
{nameError && (
<p style={{ color: "red", marginTop: "4px" }}>{nameError}</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="例:taro@example.com"
/>
</label>
{emailError && (
<p style={{ color: "red", marginTop: "4px" }}>{emailError}</p>
)}
</div>
<div style={{ marginTop: "12px" }}>
<button type="submit">登録</button>
</div>
</form>
JSXここでのポイントは、
- プレースホルダーに「例」を書くことで、ユーザーが入力イメージを持ちやすくなること
- エラーメッセージを入力欄のすぐ下に表示し、「どこが問題か」を即座に伝えていること
です。
バリデーションを「実戦の入り口」として意識する
名前・メールのルールを明確にする
バリデーション関数は、アプリの「入口のフィルター」です。 ここで「どんな入力を許すか・許さないか」を決めます。
const MAX_NAME_LENGTH = 20;
const validateName = (name) => {
if (name.trim() === "") {
return "名前は必須です。";
}
if (name.length > MAX_NAME_LENGTH) {
return `名前は${MAX_NAME_LENGTH}文字以内で入力してください。`;
}
return "";
};
const validateEmail = (email) => {
if (email.trim() === "") {
return "メールアドレスは必須です。";
}
if (!email.includes("@")) {
return "メールアドレスの形式が正しくありません。";
}
return "";
};
JSXここで意識してほしいのは、
- 「必須」「文字数」「形式」といったルールを、コードの中で明確に表現していること
- エラーメッセージに「どう直せばよいか」が分かる情報を含めていること
です。
重要な感覚:
バリデーションは「ユーザーを困らせるための壁」ではなく、 「アプリを守りつつ、ユーザーに正しい入力を促すためのガイド」です。
登録処理と一覧表示を「安全に・分かりやすく」つなぐ
ユーザーオブジェクトの扱い方を整理する
登録処理では、バリデーションを通過した入力を「ユーザーオブジェクト」として一覧に追加します。
const handleSubmit = (e) => {
e.preventDefault();
setNameError("");
setEmailError("");
const nameValidationResult = validateName(name);
const emailValidationResult = validateEmail(email);
const hasError =
nameValidationResult !== "" || emailValidationResult !== "";
if (nameValidationResult !== "") {
setNameError(nameValidationResult);
}
if (emailValidationResult !== "") {
setEmailError(emailValidationResult);
}
if (hasError) {
return;
}
const newUser = {
id: Date.now(), // 簡易的な一意ID
name,
email,
};
setUsers((prevUsers) => [...prevUsers, newUser]);
setName("");
setEmail("");
};
JSXここでのポイントは、
hasErrorという変数で「エラーがあるかどうか」を明示していること- ユーザーオブジェクトに
idを持たせることで、一覧表示や削除処理で「誰が誰か」を区別できること - 登録後に入力欄をリセットし、次の入力に備えていること
です。
一覧表示は、ユーザーオブジェクトの配列をそのまま画面に反映します。
<div style={{ marginTop: "24px" }}>
<h3>登録済みユーザー一覧</h3>
{users.length === 0 ? (
<p>まだユーザーは登録されていません。</p>
) : (
<ul style={{ paddingLeft: "16px" }}>
{users.map((user) => (
<li key={user.id} style={{ marginBottom: "4px" }}>
<span>
{user.name}({user.email})
</span>
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteUser(user.id)}
>
削除
</button>
</li>
))}
</ul>
)}
</div>
JSX削除処理を「State更新の基本パターン」として理解する
filterで「残すものだけ」を選ぶ
削除処理は、ReactのState更新の基本パターンを体現しています。
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
JSXここで起きていることは、
prevUsersは「今のユーザー一覧」filterは「条件を満たす要素だけを残す」関数user.id !== idという条件で、「削除したいユーザー以外」を残している
つまり、
「削除したいユーザーを取り除いた新しい配列」を作り、 それを
setUsersに渡している
ということです。
重要な感覚:
Reactでは「Stateを直接書き換えず、新しい値を作って置き換える」というルールがあります。 削除処理も、このルールに沿って「新しい配列を作る」という形で考えると、自然に理解できます。
DAY 36 後半版テンプレート ― 実戦寄り簡易ユーザー登録アプリ
ここまでの内容をすべて含んだ、DAY 36後半のテンプレートをまとめます。
import { useState } from "react";
const MAX_NAME_LENGTH = 20;
function SimpleUserRegisterAppFinal() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [nameError, setNameError] = useState("");
const [emailError, setEmailError] = useState("");
const [users, setUsers] = useState([]);
const validateName = (name) => {
if (name.trim() === "") {
return "名前は必須です。";
}
if (name.length > MAX_NAME_LENGTH) {
return `名前は${MAX_NAME_LENGTH}文字以内で入力してください。`;
}
return "";
};
const validateEmail = (email) => {
if (email.trim() === "") {
return "メールアドレスは必須です。";
}
if (!email.includes("@")) {
return "メールアドレスの形式が正しくありません。";
}
return "";
};
const handleSubmit = (e) => {
e.preventDefault();
setNameError("");
setEmailError("");
const nameValidationResult = validateName(name);
const emailValidationResult = validateEmail(email);
const hasError =
nameValidationResult !== "" || emailValidationResult !== "";
if (nameValidationResult !== "") {
setNameError(nameValidationResult);
}
if (emailValidationResult !== "") {
setEmailError(emailValidationResult);
}
if (hasError) {
return;
}
const newUser = {
id: Date.now(),
name,
email,
};
setUsers((prevUsers) => [...prevUsers, newUser]);
setName("");
setEmail("");
};
const handleDeleteUser = (id) => {
setUsers((prevUsers) => prevUsers.filter((user) => user.id !== id));
};
return (
<div>
<h2>DAY 36:フォーム総合演習後半 ― 簡易ユーザー登録アプリ完成版</h2>
<form onSubmit={handleSubmit}>
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="例:山田太郎"
/>
</label>
{nameError && (
<p style={{ color: "red", marginTop: "4px" }}>{nameError}</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="例:taro@example.com"
/>
</label>
{emailError && (
<p style={{ color: "red", marginTop: "4px" }}>{emailError}</p>
)}
</div>
<div style={{ marginTop: "12px" }}>
<button type="submit">登録</button>
</div>
</form>
<div style={{ marginTop: "24px" }}>
<h3>登録済みユーザー一覧</h3>
{users.length === 0 ? (
<p>まだユーザーは登録されていません。</p>
) : (
<ul style={{ paddingLeft: "16px" }}>
{users.map((user) => (
<li key={user.id} style={{ marginBottom: "4px" }}>
<span>
{user.name}({user.email})
</span>
<button
style={{ marginLeft: "8px" }}
onClick={() => handleDeleteUser(user.id)}
>
削除
</button>
</li>
))}
</ul>
)}
</div>
</div>
);
}
export default SimpleUserRegisterAppFinal;
JSXDAY 36 後半のまとめ ― 「フォームの一連の流れ」を自分の手で完結させる
DAY 36後半で定着させてほしいポイントは次の通りです。
- 入力 → バリデーション → 登録 → 一覧表示 → 削除 という一連の流れを、1つのコンポーネントの中で完結させられること。
- バリデーションは、アプリを守りつつユーザーに正しい入力を促す「入口のフィルター」であること。
- 削除処理は、「新しい配列を作ってStateを更新する」というReactの基本パターンであること。
このミニアプリの流れが腑に落ちてくると、 より大きなアプリでも、フォームまわりの設計を落ち着いて進められるようになっていきます。
