React でフォームを作るとき、入力欄の値をどう扱えばよいか迷うことはないでしょうか。React の定番は制御コンポーネント(controlled component)という考え方で、入力欄の値を useState の状態と結びつけて管理します。こうすると、入力内容をリアルタイムで検証したり、送信時にまとめて扱ったりが簡単になります。この記事では、制御コンポーネントの基本、複数入力欄のまとめ方、チェックボックスやセレクトボックスの扱い、そしてフォーム送信までを、初心者向けに具体的なコードで解説します。
目次
制御コンポーネントとは
通常の HTML の入力欄は、値をブラウザ(DOM)が自分で持っています。一方 React の制御コンポーネントでは、入力欄の値を React の状態(state)が「正」として管理し、入力欄にはその状態を value として渡します。ユーザーが文字を打つたびに onChange で状態を更新し、更新された状態が再び入力欄に反映される、という一方向の流れになります。
import { useState } from 'react';
function NameForm() {
const [name, setName] = useState('');
return (
<div>
<input
type="text"
value={name} // 状態を値として渡す
onChange={(e) => setName(e.target.value)} // 入力のたびに状態を更新
/>
<p>こんにちは、{name} さん</p>
</div>
);
}
ポイントは value={name} と onChange のペアです。ユーザーがキーを押すと onChange が発火し、e.target.value(入力された文字列)で状態 name を更新します。状態が変わると再レンダリングされ、value={name} によって入力欄に最新の値が表示されます。状態が常に入力欄の内容と一致しているため、下の <p> のように入力値をそのまま画面の別の場所で使うのも簡単です。
複数の入力欄を1つの状態でまとめる
入力欄が増えるたびに useState を増やすのは大変です。関連する項目は、1つのオブジェクトの状態にまとめると管理しやすくなります。各入力欄に name 属性を付けておき、onChange で「どの項目が変わったか」を見て更新します。
import { useState } from 'react';
function ProfileForm() {
const [form, setForm] = useState({ username: '', email: '' });
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
// 変わった項目だけを上書きする
setForm((prev) => ({ ...prev, [name]: value }));
};
return (
<form>
<input name="username" value={form.username} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
</form>
);
}
handleChange はどの入力欄からでも共通で使えます。e.target.name で変更された項目名('username' か 'email')を取り出し、[name]: value という計算されたプロパティ名でその項目だけを上書きします。...prev で既存の値を引き継いでいるので、他の項目は保持されます。入力欄が増えても handleChange を使い回せるのが利点です。
チェックボックス・セレクトボックスの扱い
テキスト以外の入力欄も、基本は「状態と結びつける」考え方は同じですが、値の取り出し方が少し違います。チェックボックスは value ではなく checked(真偽値)を使い、<select> は選択中の value を状態と結びつけます。
import { useState } from 'react';
function OptionsForm() {
const [agreed, setAgreed] = useState(false);
const [plan, setPlan] = useState('free');
return (
<div>
{/* チェックボックスは checked と e.target.checked */}
<label>
<input
type="checkbox"
checked={agreed}
onChange={(e) => setAgreed(e.target.checked)}
/>
規約に同意する
</label>
{/* select は value を状態と結びつける */}
<select value={plan} onChange={(e) => setPlan(e.target.value)}>
<option value="free">無料プラン</option>
<option value="pro">プロプラン</option>
</select>
</div>
);
}
チェックボックスはオン・オフを表すので、状態は真偽値にして checked={agreed} と e.target.checked を使います。<select> は、HTML のように <option> に selected を付けるのではなく、<select> 側の value で選択中の項目を決めるのが React 流です。どの入力部品でも「状態を value(または checked)で渡し、変更を onChange で状態に反映する」という形は共通しています。
フォームを送信する
入力値を状態で持っているので、送信処理はとても簡単です。<form> の onSubmit で状態をそのまま使えます。忘れてはいけないのが e.preventDefault() です。これを呼ばないと、ブラウザ本来のフォーム送信でページが再読み込みされてしまいます。
import { useState } from 'react';
function LoginForm() {
const [form, setForm] = useState({ email: '', password: '' });
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); // ページの再読み込みを止める
console.log('送信内容:', form);
// ここで API へ送信するなどの処理を書く
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
};
return (
<form onSubmit={handleSubmit}>
<input name="email" value={form.email} onChange={handleChange} />
<input name="password" type="password" value={form.password} onChange={handleChange} />
<button type="submit">ログイン</button>
</form>
);
}
onSubmit の中では、状態 form に入力内容がすべて揃っているので、そのまま API 送信などに使えます。制御コンポーネントの大きなメリットはここにあります。いつでも最新の入力内容が状態として手元にあるため、送信・検証・整形といった処理を状態を見るだけで書けるのです。
入力しても文字が表示されないとき
制御コンポーネントで初心者がよくハマるのが、「value は指定したのに、入力しても文字が出ない」という症状です。原因は、value を渡しているのに onChange を書き忘れている(または状態を更新していない)ことです。
制御コンポーネントでは、表示される値はあくまで状態です。onChange で状態を更新しなければ、キーを打っても状態は変わらず、value は初期値のまま固定されてしまいます。value と onChange は必ずセットで書く、と覚えておきましょう。なお「値は変えないが読み取り専用で表示したい」場合は、onChange の代わりに readOnly を付ければ警告も出ません。
まとめ
制御コンポーネントは、入力欄の値を React の状態で管理する方法です。基本の形は value={状態} と onChange のペアで、ユーザーの入力を onChange で状態に反映し、その状態を value として表示します。複数の入力欄は name 属性とオブジェクトの状態でまとめると、共通の handleChange で扱えます。チェックボックスは checked、<select> は <select> 側の value を使う点だけ押さえておきましょう。送信は onSubmit で e.preventDefault() を呼び、状態をそのまま利用します。「入力欄の値は常に状態が正」という一方向の流れを理解すれば、検証や送信を含むフォームを見通しよく組み立てられます。