- DAY 33:複数フォーム 前半 ― 「項目が増えても怖くない」フォームの基本設計
- 複数フォームの基本方針を決める
- 名前・メール・年齢をControlled Componentで作る
- textareaとselectをControlled Componentで扱う
- 複数フォームを1つのコンポーネントとしてまとめる
- DAY 33 前半のまとめ ― 「複数フォームでも型は同じ」
- DAY 33:複数フォーム 中盤 ― 「複数項目×Controlled Component」を設計目線で整理する
- 複数フォームを「1つのデータセット」として捉える
- 各項目とState・onChangeの対応関係をはっきりさせる
- 複数フォームを「Controlled Componentの集合」として見る
- フォーム全体の送信処理を設計する
- 中盤版テンプレート:複数フォーム+軽いチェック
- DAY 33 中盤のまとめ ― 「複数フォーム=複数のControlled Component+1枚のカード」
- DAY 33:複数フォーム 後半 ― 「実戦で使える複数フォーム」に仕上げる
- 複数フォームに「最低限のバリデーション」を組み込む
- エラー状態を「派生情報」として扱う
- エラーメッセージをフォームに自然に組み込む
- 送信処理に「バリデーションの最終チェック」を入れる
- DAY 33 後半版:複数フォーム+バリデーション付きテンプレート
- DAY 33 後半のまとめ ― 「複数フォーム+バリデーション」が実戦の基本形
DAY 33:複数フォーム 前半 ― 「項目が増えても怖くない」フォームの基本設計
DAY 33の前半では、 名前・メール・年齢・textarea・select をまとめて扱う「複数フォーム」を題材に、 項目が増えても迷わないフォーム設計の基本を身につけていきます。
ここまでで、単一の入力欄(名前入力フォーム)で
ユーザー入力 → onChange → State更新 → 画面に反映
という流れは体験済みです。 今回は、それを 複数項目にそのまま広げる ことがテーマになります。
複数フォームの基本方針を決める
「項目ごとにStateを持つ」から始める
まずは、シンプルに「項目ごとにStateを持つ」方針で考えます。
扱う項目は次の5つです。
- 名前(text)
- メール(email)
- 年齢(number)
- 自己紹介(textarea)
- コース選択(select)
それぞれに対応するStateを用意します。
import { useState } from "react";
function MultiFormBasic() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState("");
const [bio, setBio] = useState("");
const [course, setCourse] = useState("react");
return (
<div>
<h2>DAY 33:複数フォーム(前半)― 基本のState設計</h2>
{/* ここにフォームを作っていきます */}
</div>
);
}
export default MultiFormBasic;
JSXここでのポイントは、
- 名前・メール・年齢・自己紹介・コース選択が、それぞれ独立したStateを持っていること
courseの初期値を"react"にして、selectの初期選択を決めていること
です。
この「項目ごとにStateを持つ」という方針は、 複数フォームの一番わかりやすいスタート地点になります。
名前・メール・年齢をControlled Componentで作る
名前(text)入力欄
まずは、名前入力欄を作ります。
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
</div>
JSXここでは、
value={name}で入力欄の表示をStateに紐づけています。onChangeで入力のたびにsetNameを呼び、Stateを更新しています。
単一フォームで学んだ「Controlled Component」の基本形が、そのまま使えています。
メール(email)入力欄
次に、メール入力欄です。
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
</div>
JSXここでも、
value={email}onChange={(e) => setEmail(e.target.value)}
という形で、 メール入力欄をControlled Componentにしています。
年齢(number)入力欄
年齢は type="number" を使います。
<div style={{ marginTop: "8px" }}>
<label>
年齢:
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="年齢を入力してください"
/>
</label>
</div>
JSXここでのポイントは、
ageを文字列として扱っていること(useState(""))- 必要に応じて、後で数値に変換して使うことができること
です。
重要な感覚:
入力欄は基本的に「文字列」としてStateに保存し、 数値が必要な場面で
Number(age)のように変換する。
この方針にしておくと、 フォームの扱いがシンプルになります。
textareaとselectをControlled Componentで扱う
自己紹介(textarea)
textarea も、input と同じようにControlled Componentにできます。
<div style={{ marginTop: "8px" }}>
<label>
自己紹介:
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
placeholder="簡単な自己紹介を書いてください"
rows={4}
/>
</label>
</div>
JSXここでも、
value={bio}onChange={(e) => setBio(e.target.value)}
という形で、 自己紹介欄をStateに紐づけています。
ポイント:
textareaも「value+onChange」でControlled Componentになる。 特別な扱いは不要で、inputと同じパターンで考えられる。
コース選択(select)
最後に、select をControlled Componentとして扱います。
<div style={{ marginTop: "8px" }}>
<label>
受講コース:
<select
value={course}
onChange={(e) => setCourse(e.target.value)}
>
<option value="react">React入門コース</option>
<option value="js">JavaScript基礎コース</option>
<option value="ts">TypeScriptステップアップコース</option>
</select>
</label>
</div>
JSXここでのポイントは、
value={course}によって、選択されている項目がStatecourseに紐づいていることonChangeで選択が変わるたびにsetCourse(e.target.value)を呼んでいること
です。
重要な感覚:
selectも「value+onChange」でControlled Componentになる。 選択された値は、Stateとしていつでも参照できる。
複数フォームを1つのコンポーネントとしてまとめる
フォーム全体のコード(DAY 33 前半版)
ここまでの内容をすべてまとめると、 次のような「複数フォーム」のコンポーネントになります。
import { useState } from "react";
function MultiFormBasic() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState("");
const [bio, setBio] = useState("");
const [course, setCourse] = useState("react");
const handleSubmit = (e) => {
e.preventDefault();
alert(
`送信された情報\n` +
`名前: ${name}\n` +
`メール: ${email}\n` +
`年齢: ${age}\n` +
`自己紹介: ${bio}\n` +
`受講コース: ${course}`
);
};
return (
<div>
<h2>DAY 33:複数フォーム(前半)― 基本フォーム</h2>
<form onSubmit={handleSubmit}>
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
</div>
<div style={{ marginTop: "8px" }}>
<label>
年齢:
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="年齢を入力してください"
/>
</label>
</div>
<div style={{ marginTop: "8px" }}>
<label>
自己紹介:
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
placeholder="簡単な自己紹介を書いてください"
rows={4}
/>
</label>
</div>
<div style={{ marginTop: "8px" }}>
<label>
受講コース:
<select
value={course}
onChange={(e) => setCourse(e.target.value)}
>
<option value="react">React入門コース</option>
<option value="js">JavaScript基礎コース</option>
<option value="ts">TypeScriptステップアップコース</option>
</select>
</label>
</div>
<div style={{ marginTop: "12px" }}>
<button type="submit">送信</button>
</div>
</form>
<hr />
<div>
<h3>現在の入力内容</h3>
<p>名前:{name}</p>
<p>メール:{email}</p>
<p>年齢:{age}</p>
<p>自己紹介:{bio}</p>
<p>受講コース:{course}</p>
</div>
</div>
);
}
export default MultiFormBasic;
JSXこのコンポーネントは、
- 5つの入力項目(名前・メール・年齢・textarea・select)を すべてControlled Componentとして扱っています。
- それぞれが独立したStateを持ち、
value+onChangeで入力内容と同期しています。 handleSubmitで、すべてのStateから値を取り出してまとめて扱っています。
ここで大事なのは、「項目が増えてもやっていることは同じ」という感覚です。
DAY 33 前半のまとめ ― 「複数フォームでも型は同じ」
DAY 33前半で、自分の中に定着させてほしいのは次のポイントです。
- 複数フォームでも、基本は 項目ごとにStateを持ち、value+onChangeでControlled Componentにする という形で考えられること。
- 名前・メール・年齢・textarea・selectのどれも、 「State → value → onChange → State更新 →画面に反映」という同じ流れで扱えること。
selectやtextareaも特別扱いではなく、inputと同じパターンでControlled Componentとして設計できること。handleSubmitで、複数のStateから値をまとめて取り出すことで、 「フォーム全体としての送信処理」を自然に書けること。
この「複数項目でも型は同じ」という感覚が身につくと、 この先のフォーム・コンポーネント設計(バリデーション・エラー表示・ネストしたフォームなど)が、 すべてこの基本形の延長として、落ち着いて組み立てられるようになっていきます。
DAY 33:複数フォーム 中盤 ― 「複数項目×Controlled Component」を設計目線で整理する
DAY 33の中盤では、前半で作った
- 名前
- メール
- 年齢
- textarea(自己紹介)
- select(コース選択)
を、「設計目線」で整理し直すことを目標にします。
単に「動くフォーム」から一歩進んで、
- どの項目がどのStateと対応しているか
- どの入力がどのonChangeで処理されているか
- どうやって「フォーム全体」として扱うか
を、意識して見ていきます。
複数フォームを「1つのデータセット」として捉える
項目ごとのStateを「1枚のカード」としてイメージする
前半では、次のように項目ごとにStateを持ちました。
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState("");
const [bio, setBio] = useState("");
const [course, setCourse] = useState("react");
JSX中盤では、これを「バラバラの変数」ではなく、
1人のユーザー情報カード
としてイメージしてみてください。
- 名前 → ユーザーの名前
- メール → 連絡先
- 年齢 → プロフィール情報
- 自己紹介 → テキストの説明
- コース選択 → 受講プラン
この「1枚のカード」を、フォームで編集しているイメージです。
重要な感覚:
複数フォームは、「複数のStateを持つコンポーネント」ではなく、 「1つの意味のあるデータ(ユーザー情報)を編集するUI」として捉える。
この視点があると、 フォーム設計がぐっと整理されて見えてきます。
各項目とState・onChangeの対応関係をはっきりさせる
名前入力欄:text+State+onChange
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
JSX- State:
name - 更新関数:
setName - 入力欄:
input type="text" - 流れ: ユーザー入力 → onChange →
setName→name更新 → 画面に反映
メール入力欄:email+State+onChange
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
JSX- State:
email - 更新関数:
setEmail - 入力欄:
input type="email" - 流れ: ユーザー入力 → onChange →
setEmail→email更新 → 画面に反映
年齢入力欄:number+State+onChange
<label>
年齢:
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="年齢を入力してください"
/>
</label>
JSX- State:
age(文字列として保持) - 更新関数:
setAge - 入力欄:
input type="number" - 流れ: ユーザー入力 → onChange →
setAge→age更新 → 画面に反映
ここでのポイント:
入力欄の値は基本「文字列」としてStateに保存し、 数値が必要な場面で
Number(age)に変換する。
自己紹介:textarea+State+onChange
<label>
自己紹介:
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
placeholder="簡単な自己紹介を書いてください"
rows={4}
/>
</label>
JSX- State:
bio - 更新関数:
setBio - 入力欄:
textarea - 流れ: ユーザー入力 → onChange →
setBio→bio更新 → 画面に反映
コース選択:select+State+onChange
<label>
受講コース:
<select
value={course}
onChange={(e) => setCourse(e.target.value)}
>
<option value="react">React入門コース</option>
<option value="js">JavaScript基礎コース</option>
<option value="ts">TypeScriptステップアップコース</option>
</select>
</label>
JSX- State:
course - 更新関数:
setCourse - 入力欄:
select+option - 流れ: ユーザー選択 → onChange →
setCourse→course更新 → 画面に反映
重要な感覚:
inputだけでなく、textareaやselectも 「value+onChange+State」の同じパターンで扱える。
複数フォームを「Controlled Componentの集合」として見る
フォーム全体を分解してみる
中盤では、フォーム全体を「Controlled Componentの集合」として見てみます。
<form onSubmit={handleSubmit}>
{/* 名前 */}
{/* メール */}
{/* 年齢 */}
{/* 自己紹介 */}
{/* 受講コース */}
{/* 送信ボタン */}
</form>
JSXそれぞれの入力欄は、
- 自分専用のStateを持ち
- 自分専用のonChangeでStateを更新し
- 自分専用のvalueで表示を制御している
という意味で、 独立したControlled Component です。
それがフォームの中に並んでいるイメージです。
ポイント:
「複数フォーム=1つの巨大なコンポーネント」ではなく、 「複数のControlled Componentが並んだフォーム」として捉える。
この視点があると、 項目が増えても「同じパターンの繰り返し」として落ち着いて扱えます。
フォーム全体の送信処理を設計する
handleSubmitで「1枚のカード」として扱う
前半で書いた送信処理を、設計目線で見直します。
const handleSubmit = (e) => {
e.preventDefault();
alert(
`送信された情報\n` +
`名前: ${name}\n` +
`メール: ${email}\n` +
`年齢: ${age}\n` +
`自己紹介: ${bio}\n` +
`受講コース: ${course}`
);
};
JSXここで起きていることは、
e.preventDefault()でページリロードを防いでいる- 各Stateから値を取り出して、1つのメッセージとしてまとめている
という流れです。
重要な感覚:
フォーム送信時には、「複数のState」を 「1つの意味のあるデータ(ユーザー情報)」としてまとめて扱う。
この考え方は、 APIに送信するときにもそのまま使えます。
例えば、次のような形です。
const payload = {
name,
email,
age: Number(age),
bio,
course,
};
// fetch("/api/register", { method: "POST", body: JSON.stringify(payload) });
JSX中盤版テンプレート:複数フォーム+軽いチェック
DAY 33中盤では、 前半のフォームに少しだけ「チェック」を足したテンプレートを示します。
import { useState } from "react";
function MultiFormMid() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState("");
const [bio, setBio] = useState("");
const [course, setCourse] = useState("react");
const handleSubmit = (e) => {
e.preventDefault();
if (name.trim() === "" || email.trim() === "") {
alert("名前とメールは必須です。");
return;
}
alert(
`送信された情報\n` +
`名前: ${name}\n` +
`メール: ${email}\n` +
`年齢: ${age}\n` +
`自己紹介: ${bio}\n` +
`受講コース: ${course}`
);
};
const isNameEmpty = name.trim() === "";
const isEmailEmpty = email.trim() === "";
return (
<div>
<h2>DAY 33:複数フォーム(中盤)― 基本フォーム+軽いチェック</h2>
<form onSubmit={handleSubmit}>
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
{isNameEmpty && (
<p style={{ color: "red" }}>名前は必須です。</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
{isEmailEmpty && (
<p style={{ color: "red" }}>メールは必須です。</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
年齢:
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="年齢を入力してください"
/>
</label>
</div>
<div style={{ marginTop: "8px" }}>
<label>
自己紹介:
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
placeholder="簡単な自己紹介を書いてください"
rows={4}
/>
</label>
</div>
<div style={{ marginTop: "8px" }}>
<label>
受講コース:
<select
value={course}
onChange={(e) => setCourse(e.target.value)}
>
<option value="react">React入門コース</option>
<option value="js">JavaScript基礎コース</option>
<option value="ts">TypeScriptステップアップコース</option>
</select>
</label>
</div>
<div style={{ marginTop: "12px" }}>
<button type="submit">送信</button>
</div>
</form>
<hr />
<div>
<h3>現在の入力内容</h3>
<p>名前:{name}</p>
<p>メール:{email}</p>
<p>年齢:{age}</p>
<p>自己紹介:{bio}</p>
<p>受講コース:{course}</p>
</div>
</div>
);
}
export default MultiFormMid;
JSXこのテンプレートでは、
- 各項目がControlled Componentとして設計されている
- 名前とメールに対して「必須チェック」を入れている
- フォーム全体を「1つのユーザー情報」として送信している
という形で、 複数フォームを設計目線で扱う練習になっています。
DAY 33 中盤のまとめ ― 「複数フォーム=複数のControlled Component+1枚のカード」
DAY 33中盤で、自分の中に定着させてほしいのは次のポイントです。
- 複数フォームは、 複数のControlled Component(名前・メール・年齢・textarea・select)が並んだものとして捉えられること。
- 各項目は、 自分専用のState・value・onChangeを持ち、 「入力→State→画面」の同じ流れで動いていること。
- フォーム送信時には、 複数のStateを「1枚の意味のあるデータ(ユーザー情報)」としてまとめて扱うことができること。
- 名前・メール・年齢・textarea・selectという5つの項目を通して、 「項目が増えてもやっていることは同じ」という感覚が持てること。
この感覚が身につくと、 この先のフォーム・コンポーネント設計(バリデーション強化、エラー管理、オブジェクトStateへの移行など)が、 すべてこの「複数のControlled Component+1枚のカード」という枠組みの中で、 落ち着いて考えられるようになっていきます。
DAY 33:複数フォーム 後半 ― 「実戦で使える複数フォーム」に仕上げる
DAY 33の後半では、前半・中盤で作ってきた
- 名前
- メール
- 年齢
- textarea(自己紹介)
- select(コース選択)
を使った複数フォームを、「実戦で使えるフォーム」レベルに仕上げることを目標にします。
ここで意識したいのは、
- 入力チェック(必須・形式・範囲)をどう組み込むか
- エラーメッセージをどう表示するか
- 「送信してよい状態か」をどう判定するか
という、現場でよく出てくるフォーム設計の考え方です。
複数フォームに「最低限のバリデーション」を組み込む
まずは何をチェックするかを決める
今回のフォームでは、次のようなルールを決めます。
- 名前:必須(空はNG)
- メール:必須+「@」を含む簡易チェック
- 年齢:任意だが、入力されている場合は 0〜120 の範囲に収める
- 自己紹介:任意(今回はチェックしない)
- コース選択:必須(selectの値が空になるケースは避ける)
このルールを、Stateと組み合わせて実装していきます。
エラー状態を「派生情報」として扱う
Stateそのものではなく「Stateから計算される値」としてエラーを考える
フォームのエラー状態は、 「別のState」として持つこともできますが、 まずは 既存のStateから計算される値 として扱う方がシンプルです。
例えば、次のように書きます。
const isNameEmpty = name.trim() === "";
const isEmailEmpty = email.trim() === "";
const isEmailInvalidFormat =
!isEmailEmpty && !email.includes("@");
const hasAgeValue = age.trim() !== "";
const ageNumber = hasAgeValue ? Number(age) : null;
const isAgeOutOfRange =
hasAgeValue && (ageNumber < 0 || ageNumber > 120);
const isFormValid =
!isNameEmpty &&
!isEmailEmpty &&
!isEmailInvalidFormat &&
!isAgeOutOfRange;
JSXここでのポイントは、
- エラー状態を「Stateそのもの」ではなく、「Stateから導かれる派生情報」として扱っていること
isFormValidを使って、「送信してよいかどうか」を一括で判定していること
です。
重要な感覚:
入力値(State)と、 その入力値から計算される「エラー状態」や「有効/無効状態」は、 別々に考えると整理しやすくなります。
エラーメッセージをフォームに自然に組み込む
項目ごとにエラーメッセージを表示する
先ほどの派生情報を使って、 各項目の下にエラーメッセージを表示してみます。
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
{isNameEmpty && (
<p style={{ color: "red" }}>名前は必須です。</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
{isEmailEmpty && (
<p style={{ color: "red" }}>メールは必須です。</p>
)}
{isEmailInvalidFormat && (
<p style={{ color: "red" }}>メールアドレスの形式が正しくありません。</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
年齢:
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="年齢を入力してください(任意)"
/>
</label>
{isAgeOutOfRange && (
<p style={{ color: "red" }}>
年齢は0〜120の範囲で入力してください。
</p>
)}
</div>
JSXここで起きていることは、
- 入力値に応じてエラー状態が変わる
- エラー状態に応じてメッセージが表示/非表示になる
という流れです。
ポイント:
Controlled Componentでフォームを作ると、 「入力値 → エラー状態 → メッセージ表示」という流れを すべてReactの中で完結させることができます。
送信処理に「バリデーションの最終チェック」を入れる
handleSubmitで「最後の門番」をする
フォーム送信時には、 isFormValid を使って最終チェックを行います。
const handleSubmit = (e) => {
e.preventDefault();
if (!isFormValid) {
alert("入力内容に問題があります。各項目を確認してください。");
return;
}
alert(
`送信された情報\n` +
`名前: ${name}\n` +
`メール: ${email}\n` +
`年齢: ${age}\n` +
`自己紹介: ${bio}\n` +
`受講コース: ${course}`
);
// 送信後にフォームをリセットする例
setName("");
setEmail("");
setAge("");
setBio("");
setCourse("react");
};
JSXここでのポイントは、
isFormValidを使って、「フォーム全体として有効かどうか」を一括で判定していること- 無効な場合は送信処理を止めて、ユーザーに確認を促していること
- 有効な場合は送信し、その後フォームを初期状態に戻していること
です。
重要な感覚:
「項目ごとのエラー表示」と「フォーム全体の有効/無効判定」は、 同じ派生情報(
isNameEmptyなど)から計算できる。
DAY 33 後半版:複数フォーム+バリデーション付きテンプレート
ここまでの内容をすべてまとめた、 DAY 33後半の複数フォームテンプレートを示します。
import { useState } from "react";
function MultiFormFinal() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [age, setAge] = useState("");
const [bio, setBio] = useState("");
const [course, setCourse] = useState("react");
// 派生情報(エラー状態など)
const isNameEmpty = name.trim() === "";
const isEmailEmpty = email.trim() === "";
const isEmailInvalidFormat =
!isEmailEmpty && !email.includes("@");
const hasAgeValue = age.trim() !== "";
const ageNumber = hasAgeValue ? Number(age) : null;
const isAgeOutOfRange =
hasAgeValue && (ageNumber < 0 || ageNumber > 120);
const isFormValid =
!isNameEmpty &&
!isEmailEmpty &&
!isEmailInvalidFormat &&
!isAgeOutOfRange;
const handleSubmit = (e) => {
e.preventDefault();
if (!isFormValid) {
alert("入力内容に問題があります。各項目を確認してください。");
return;
}
alert(
`送信された情報\n` +
`名前: ${name}\n` +
`メール: ${email}\n` +
`年齢: ${age}\n` +
`自己紹介: ${bio}\n` +
`受講コース: ${course}`
);
// フォームのリセット
setName("");
setEmail("");
setAge("");
setBio("");
setCourse("react");
};
return (
<div>
<h2>DAY 33:複数フォーム(後半)― バリデーション付きフォーム</h2>
<form onSubmit={handleSubmit}>
<div>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="名前を入力してください"
/>
</label>
{isNameEmpty && (
<p style={{ color: "red" }}>名前は必須です。</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
メール:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="メールアドレスを入力してください"
/>
</label>
{isEmailEmpty && (
<p style={{ color: "red" }}>メールは必須です。</p>
)}
{isEmailInvalidFormat && (
<p style={{ color: "red" }}>
メールアドレスの形式が正しくありません。
</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
年齢:
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
placeholder="年齢を入力してください(任意)"
/>
</label>
{isAgeOutOfRange && (
<p style={{ color: "red" }}>
年齢は0〜120の範囲で入力してください。
</p>
)}
</div>
<div style={{ marginTop: "8px" }}>
<label>
自己紹介:
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
placeholder="簡単な自己紹介を書いてください"
rows={4}
/>
</label>
</div>
<div style={{ marginTop: "8px" }}>
<label>
受講コース:
<select
value={course}
onChange={(e) => setCourse(e.target.value)}
>
<option value="react">React入門コース</option>
<option value="js">JavaScript基礎コース</option>
<option value="ts">TypeScriptステップアップコース</option>
</select>
</label>
</div>
<div style={{ marginTop: "12px" }}>
<button type="submit" disabled={!isFormValid}>
送信
</button>
</div>
</form>
<hr />
<div>
<h3>現在の入力内容</h3>
<p>名前:{name}</p>
<p>メール:{email}</p>
<p>年齢:{age}</p>
<p>自己紹介:{bio}</p>
<p>受講コース:{course}</p>
<p>フォーム有効か:{isFormValid ? "はい" : "いいえ"}</p>
</div>
</div>
);
}
export default MultiFormFinal;
JSXDAY 33 後半のまとめ ― 「複数フォーム+バリデーション」が実戦の基本形
DAY 33後半で、自分の中に残してほしいのは次のポイントです。
- 複数フォームでも、 名前・メール・年齢・textarea・selectのすべてがControlled Componentとして動いていること。
- エラー状態は、 入力値(State)から計算される「派生情報」として扱うと、 項目ごとのメッセージ表示やフォーム全体の有効/無効判定を整理しやすいこと。
isFormValidのような「フォーム全体の有効判定」を用意しておくと、 送信ボタンのdisabledやhandleSubmitの最終チェックに使えて便利なこと。- このテンプレートが、 実際のアプリで使う複数フォーム(ユーザー登録・プロフィール編集・問い合わせフォームなど)の 基本形としてそのまま応用できること。
この「複数フォーム+バリデーション付きControlled Component」の型が身につくと、 この先のフォーム・コンポーネント設計(オブジェクトStateへの移行、エラー管理の共通化、API連携など)が、 すべてこの基本形の延長として、自然に組み立てられるようになっていきます。
