フォーム処理の全体イメージ
React / Next.js で「実用的なアプリ」を作ろうとすると、必ず出てくるのがフォームです。
ログインフォーム、問い合わせフォーム、検索フォーム、プロフィール編集画面…。
どれも本質的には「入力値を state で管理して、送信時にまとめて処理する」だけです。
ここでは、次の流れで整理していきます。
- input の管理(「入力された値をどう持つか」)
- submit 処理(「送信ボタンが押されたときに何をするか」)
- 入力チェック(バリデーション)
- よくある実装ミス
一つひとつ分解すれば、フォームはそこまで怖くありません。
input管理(入力値を state で持つ)
「入力欄=state」として考える
React では、フォームの入力値を「コンポーネントの state として持つ」のが基本です。
これを「制御されたコンポーネント(controlled component)」と呼びます。
シンプルな名前入力フォームを例にします。
"use client";
import { useState } from "react";
export default function NameForm() {
const [name, setName] = useState("");
return (
<main style={{ padding: "24px" }}>
<h1>名前フォーム</h1>
<p>あなたの名前を入力してください。</p>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
style={{ padding: "8px", fontSize: "16px", width: "240px" }}
/>
<p style={{ marginTop: "16px" }}>今の入力値: {name}</p>
</main>
);
}
TSXここで重要なのは次の 2 点です。
value={name}
入力欄の中身は、常に state name によって決まる。
onChange={(e) => setName(e.target.value)}
ユーザーが入力するたびに、setName で state を更新する。
つまり、
ユーザーがキーを打つ
onChange が発火する
state が更新される
再レンダリングされて、value={name} によって input に反映される
というループになっています。
「入力欄の中身は、常に state の値の“写し鏡”」というイメージを持つと分かりやすいです。
複数の input を管理する
フォームが少し大きくなると、入力欄が増えます。
例えば「名前+メールアドレス」のフォーム。
"use client";
import { useState } from "react";
export default function ContactForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
return (
<main style={{ padding: "24px" }}>
<h1>お問い合わせフォーム</h1>
<div style={{ marginBottom: "12px" }}>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
style={{ marginLeft: "8px", padding: "4px 8px" }}
/>
</label>
</div>
<div style={{ marginBottom: "12px" }}>
<label>
メールアドレス:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{ marginLeft: "8px", padding: "4px 8px" }}
/>
</label>
</div>
<p>名前: {name}</p>
<p>メール: {email}</p>
</main>
);
}
TSX最初のうちは、こうやって「入力欄ごとに state を分ける」書き方で十分です。
慣れてきたら、オブジェクト 1 つにまとめるパターンもありますが、
初心者の段階では「1 input = 1 state」で考えたほうが混乱しにくいです。
submit処理(送信ボタンが押されたとき)
formタグと onSubmit を使う
フォームの送信処理は、<form> タグと onSubmit を使うのが基本です。
"use client";
import { useState } from "react";
export default function SimpleForm() {
const [name, setName] = useState("");
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault(); // ブラウザのデフォルト送信(ページリロード)を止める
alert(`送信された名前: ${name}`);
}
return (
<main style={{ padding: "24px" }}>
<h1>フォーム送信の基本</h1>
<form onSubmit={handleSubmit}>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
style={{ marginLeft: "8px" }}
/>
</label>
<div style={{ marginTop: "12px" }}>
<button type="submit">送信</button>
</div>
</form>
</main>
);
}
TSXここで一番大事なのは e.preventDefault() です。
ブラウザのデフォルトのフォーム送信は、
- ページをリロードして
action先にデータを送る
という動きをします。
React / Next.js の SPA 的なフォームでは、ページリロードはしたくないので、onSubmit の中で e.preventDefault() を呼んで「デフォルトの送信を止める」のが定番です。
その上で、
- state に入っている値をまとめて使う(API に送る、アラートを出すなど)
- 必要なら入力値をリセットする
という処理を書きます。
送信後に入力欄をクリアする
送信後にフォームを空にしたい場合は、state を初期値に戻します。
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
alert(`送信された名前: ${name}`);
setName(""); // 入力欄を空に戻す
}
TSX「フォームの中身を変えたいときは、input ではなく state を変える」
というルールはここでも同じです。
入力チェック(バリデーション)
まずは「必須チェック」から
入力チェック(バリデーション)は、いきなり完璧を目指さなくて大丈夫です。
最初は「必須項目が空じゃないか」を見るだけでも立派なバリデーションです。
"use client";
import { useState } from "react";
export default function ContactForm() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [error, setError] = useState<string | null>(null);
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
if (!name || !email) {
setError("名前とメールアドレスは必須です。");
return;
}
setError(null);
alert(`送信しました!\n名前: ${name}\nメール: ${email}`);
setName("");
setEmail("");
}
return (
<main style={{ padding: "24px" }}>
<h1>お問い合わせフォーム</h1>
<form onSubmit={handleSubmit}>
<div style={{ marginBottom: "12px" }}>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
style={{ marginLeft: "8px" }}
/>
</label>
</div>
<div style={{ marginBottom: "12px" }}>
<label>
メールアドレス:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{ marginLeft: "8px" }}
/>
</label>
</div>
{error && (
<p style={{ color: "red", marginBottom: "12px" }}>{error}</p>
)}
<button type="submit">送信</button>
</form>
</main>
);
}
TSXここでのポイントは、
errorという state を用意して、エラーメッセージをそこに入れる- エラーがあるときだけエラーメッセージを表示する
- エラーがなければ送信処理を続ける
という流れです。
もう少しだけ踏み込んだチェック(メール形式など)
簡単なメール形式チェックを入れることもできます。
正規表現を使うと本格的になりますが、最初は「@ が含まれているか」くらいでも十分です。
if (!email.includes("@")) {
setError("メールアドレスの形式が正しくありません。");
return;
}
TSX大事なのは、「チェックに引っかかったらそこで処理を止める」ことです。return で早めに抜けることで、
「エラーがあるのに送信処理が進んでしまう」ことを防げます。
よくある実装ミス
1. input に value だけ書いて onChange を忘れる
例えば、こんなコードです。
<input type="text" value={name} />
TSXvalue だけ指定して onChange を書かないと、
入力欄が「読み取り専用」になってしまいます。
React は、「value を指定したら、それは制御されたコンポーネント」とみなすので、
ユーザーが入力しても state が変わらず、結果として見た目も変わりません。
必ず、
value={state} と onChange={...} はセット
と覚えておきましょう。
2. form の onSubmit で preventDefault を忘れる
onSubmit の中で e.preventDefault() を呼び忘れると、
送信ボタンを押した瞬間にページがリロードされます。
React / Next.js の SPA 的なフォームでは、
「ページリロードせずに、JS の中で処理を完結させる」のが基本なので、handleSubmit の最初の 1 行はほぼお約束でこうなります。
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault();
// ここから自分の処理
}
TSX3. 入力チェックをしているのに、エラーを画面に出していない
バリデーションをしていても、
エラーを console.log にだけ出して終わり、というパターンもよくあります。
ユーザーからすると、
- 送信ボタンを押しても何も起きない
- 何が悪いのか分からない
という状態になってしまいます。
必ず、
- エラー内容を state に入れる
- 画面上に「何が問題か」を表示する
という 2 ステップまでセットで考えましょう。
4. state を増やしすぎて自分で混乱する
フォームが大きくなると、name, email, password, confirmPassword, age, address…
と state がどんどん増えていきます。
最初のうちはそれでも構いませんが、
「どの state がどの input と対応しているか分からなくなってきた」と感じたら、
オブジェクト 1 つにまとめることも検討してよいです。
ただし、初心者の段階では、
まずは「1 input = 1 state」でフォームの流れに慣れるほうが大事です。
いきなり高度な抽象化をしようとすると、逆に分かりにくくなります。
まとめと練習の方向性
ここまでのポイントを整理すると、
フォームの入力値は、useState で管理し、value と onChange で「入力欄=state」の関係を作る。
送信処理は <form onSubmit={...}> と e.preventDefault() を使い、state に入っている値をまとめて扱う。
入力チェックは、まずは「必須チェック」から始め、エラーは state に入れて画面に表示する。
よくあるミスは「value だけ指定して onChange を忘れる」「preventDefault を忘れてページがリロードされる」「エラーをユーザーに見せない」などで、ここを意識するだけで一気に実務寄りのフォームになる。
練習としては、
- 名前+メール+メッセージの 3 項目フォーム
- 必須チェック+メール形式チェック
- 送信成功時に「ありがとうございます!」メッセージを表示
という小さな問い合わせフォームを、自分の手で一から書いてみるのがおすすめです。
フォームが一つ作れるようになると、「アプリを作っている感」が一気に増してきます。
