Reactでフォームを扱う際は、入力値をstateで管理する制御コンポーネントのパターンが基本です。テキスト入力、セレクトボックス、チェックボックスなど、さまざまなフォーム要素をReactで管理する方法を解説します。
基本的な使い方
sample.jsx
import React, { useState } from 'react';
function SimpleRegistrationForm() {
// フォームの状態を管理
const [formData, setFormData] = useState({
username: '',
email: '',
password: '',
gender: '',
agreeToTerms: false,
role: 'user'
});
// エラーメッセージの状態
const [errors, setErrors] = useState({});
// 送信成功メッセージ
const [submitSuccess, setSubmitSuccess] = useState(false);
// 入力フィールドの変更ハンドラー
const handleChange = (e) => {
const { name, value, type, checked } = e.target;
// チェックボックスの場合はchecked値を、それ以外はvalue値を使用
setFormData(prevData => ({
...prevData,
[name]: type === 'checkbox' ? checked : value
}));
// 入力時にエラーをクリア
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: null
}));
}
};
// フォームのバリデーション
const validateForm = () => {
const newErrors = {};
// ユーザー名の検証
if (!formData.username.trim()) {
newErrors.username = 'ユーザー名は必須です';
}
// メールアドレスの検証
if (!formData.email.trim()) {
newErrors.email = 'メールアドレスは必須です';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = '有効なメールアドレスを入力してください';
}
// パスワードの検証
if (!formData.password) {
newErrors.password = 'パスワードは必須です';
} else if (formData.password.length < 6) {
newErrors.password = 'パスワードは6文字以上必要です';
}
// 利用規約の同意
if (!formData.agreeToTerms) {
newErrors.agreeToTerms = '利用規約に同意する必要があります';
}
return newErrors;
};
// フォーム送信ハンドラー
const handleSubmit = (e) => {
e.preventDefault();
// バリデーション実行
const validationErrors = validateForm();
if (Object.keys(validationErrors).length > 0) {
// エラーがある場合、エラー状態を更新
setErrors(validationErrors);
} else {
// エラーがない場合、フォームを送信
console.log('送信データ:', formData);
// 実際のアプリではここでAPIにデータを送信
// この例では成功メッセージを表示
setSubmitSuccess(true);
// フォームをリセット
setFormData({
username: '',
email: '',
password: '',
gender: '',
agreeToTerms: false,
role: 'user'
});
// 3秒後に成功メッセージを非表示
setTimeout(() => {
setSubmitSuccess(false);
}, 3000);
}
};
// スタイル定義
const styles = {
formContainer: {
maxWidth: '500px',
margin: '0 auto',
padding: '20px',
backgroundColor: '#f9f9f9',
borderRadius: '8px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)'
},
title: {
textAlign: 'center',
color: '#333',
marginBottom: '20px'
},
formGroup: {
marginBottom: '15px'
},
label: {
display: 'block',
marginBottom: '5px',
fontWeight: 'bold'
},
input: {
width: '100%',
padding: '8px',
border: '1px solid #ddd',
borderRadius: '4px',
fontSize: '16px'
},
selectBox: {
width: '100%',
padding: '8px',
border: '1px solid #ddd',
borderRadius: '4px',
fontSize: '16px',
backgroundColor: 'white'
},
radioGroup: {
display: 'flex',
gap: '15px'
},
radioLabel: {
display: 'flex',
alignItems: 'center',
cursor: 'pointer'
},
checkboxLabel: {
display: 'flex',
alignItems: 'center',
cursor: 'pointer'
},
checkbox: {
marginRight: '8px'
},
error: {
color: 'red',
fontSize: '14px',
marginTop: '5px'
},
submitButton: {
width: '100%',
padding: '10px',
backgroundColor: '#4CAF50',
color: 'white',
border: 'none',
borderRadius: '4px',
fontSize: '16px',
cursor: 'pointer'
},
successMessage: {
backgroundColor: '#dff0d8',
color: '#3c763d',
padding: '10px',
borderRadius: '4px',
textAlign: 'center',
marginBottom: '15px'
}
};
return (
<div style={styles.formContainer}>
<h2 style={styles.title}>ユーザー登録</h2>
{/* 送信成功メッセージ */}
{submitSuccess && (
<div style={styles.successMessage}>
登録が完了しました!
</div>
)}
<form onSubmit={handleSubmit}>
{/* ユーザー名 */}
<div style={styles.formGroup}>
<label style={styles.label} htmlFor="username">ユーザー名</label>
<input
style={styles.input}
type="text"
id="username"
name="username"
value={formData.username}
onChange={handleChange}
/>
{errors.username && <p style={styles.error}>{errors.username}</p>}
</div>
{/* メールアドレス */}
<div style={styles.formGroup}>
<label style={styles.label} htmlFor="email">メールアドレス</label>
<input
style={styles.input}
type="email"
id="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <p style={styles.error}>{errors.email}</p>}
</div>
{/* パスワード */}
<div style={styles.formGroup}>
<label style={styles.label} htmlFor="password">パスワード</label>
<input
style={styles.input}
type="password"
id="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <p style={styles.error}>{errors.password}</p>}
</div>
{/* 性別(ラジオボタン) */}
<div style={styles.formGroup}>
<label style={styles.label}>性別</label>
<div style={styles.radioGroup}>
<label style={styles.radioLabel}>
<input
type="radio"
name="gender"
value="male"
checked={formData.gender === 'male'}
onChange={handleChange}
/>
<span style={{ marginLeft: '5px' }}>男性</span>
</label>
<label style={styles.radioLabel}>
<input
type="radio"
name="gender"
value="female"
checked={formData.gender === 'female'}
onChange={handleChange}
/>
<span style={{ marginLeft: '5px' }}>女性</span>
</label>
<label style={styles.radioLabel}>
<input
type="radio"
name="gender"
value="other"
checked={formData.gender === 'other'}
onChange={handleChange}
/>
<span style={{ marginLeft: '5px' }}>その他</span>
</label>
</div>
</div>
{/* 役割(セレクトボックス) */}
<div style={styles.formGroup}>
<label style={styles.label} htmlFor="role">役割</label>
<select
style={styles.selectBox}
id="role"
name="role"
value={formData.role}
onChange={handleChange}
>
<option value="user">一般ユーザー</option>
<option value="editor">編集者</option>
<option value="admin">管理者</option>
</select>
</div>
{/* 利用規約(チェックボックス) */}
<div style={styles.formGroup}>
<label style={styles.checkboxLabel}>
<input
style={styles.checkbox}
type="checkbox"
name="agreeToTerms"
checked={formData.agreeToTerms}
onChange={handleChange}
/>
利用規約に同意します
</label>
{errors.agreeToTerms && <p style={styles.error}>{errors.agreeToTerms}</p>}
</div>
{/* 送信ボタン */}
<button style={styles.submitButton} type="submit">
登録する
</button>
</form>
</div>
);
}
export default SimpleRegistrationForm;
説明
Step 1基本的なフォーム処理
Reactでフォームを扱う場合、主に「制御されたコンポーネント(Controlled Components)」と「非制御コンポーネント(Uncontrolled Components)」の2つのアプローチがあります。
制御されたコンポーネントは、Reactの状態(state)によってフォームの入力値を管理するアプローチです:
import React, { useState } from 'react'; function SimpleForm() { const [name, setName] = useState(''); const handleChange = (event) => { setName(event.target.value); }; const handleSubmit = (event) => { event.preventDefault(); alert(`こんにちは、${name}さん!`); }; return ( <form onSubmit={handleSubmit}> <label> 名前: <input type="text" value={name} onChange={handleChange} /> </label> <button type="submit">送信</button> </form> ); }
Step 2複数の入力フィールドの処理
複数の入力フィールドがある場合、それぞれに状態を作成するか、オブジェクトを使用して一括管理できます:
import React, { useState } from 'react'; function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', message: '' }); const handleChange = (event) => { const { name, value } = event.target; setFormData({ ...formData, // 既存のフォームデータをコピー [name]: value // 変更されたフィールドだけを更新 }); }; const handleSubmit = (event) => { event.preventDefault(); console.log('送信されたデータ:', formData); // ここでAPIにデータを送信するなどの処理 }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="name">名前:</label> <input type="text" id="name" name="name" value={formData.name} onChange={handleChange} /> </div> <div> <label htmlFor="email">メールアドレス:</label> <input type="email" id="email" name="email" value={formData.email} onChange={handleChange} /> </div> <div> <label htmlFor="message">メッセージ:</label> <textarea id="message" name="message" value={formData.message} onChange={handleChange} /> </div> <button type="submit">送信</button> </form> ); }
この例では:
- フォームデータを一つのオブジェクトとして管理
- 入力フィールドには
name属性を設定 - 計算プロパティ名
[name]: valueを使用して動的に更新
Step 3フォームバリデーション
ユーザー入力を検証する基本的な方法は、stateに検証エラーを追加することです:
import React, { useState } from 'react'; function SignupForm() { const [formData, setFormData] = useState({ username: '', email: '', password: '', confirmPassword: '' }); const [errors, setErrors] = useState({}); const handleChange = (event) => { const { name, value } = event.target; setFormData({ ...formData, [name]: value }); // フィールド変更時にエラーをクリア if (errors[name]) { setErrors({ ...errors, [name]: null }); } }; const validateForm = () => { const newErrors = {}; // ユーザー名の検証 if (!formData.username.trim()) { newErrors.username = 'ユーザー名は必須です'; } else if (formData.username.length < 3) { newErrors.username = 'ユーザー名は3文字以上必要です'; } // メールアドレスの検証 if (!formData.email.trim()) { newErrors.email = 'メールアドレスは必須です'; } else if (!/\S+@\S+\.\S+/.test(formData.email)) { newErrors.email = '有効なメールアドレスを入力してください'; } // パスワードの検証 if (!formData.password) { newErrors.password = 'パスワードは必須です'; } else if (formData.password.length < 6) { newErrors.password = 'パスワードは6文字以上必要です'; } // パスワード確認 if (formData.password !== formData.confirmPassword) { newErrors.confirmPassword = 'パスワードが一致しません'; } return newErrors; }; const handleSubmit = (event) => { event.preventDefault(); // フォームの検証 const formErrors = validateForm(); if (Object.keys(formErrors).length > 0) { // エラーがある場合 setErrors(formErrors); } else { // エラーがない場合、データを送信 console.log('送信データ:', formData); // ここでAPIにデータを送信する alert('登録が完了しました!'); } }; return ( <form onSubmit={handleSubmit}> <div> <label htmlFor="username">ユーザー名:</label> <input type="text" id="username" name="username" value={formData.username} onChange={handleChange} /> {errors.username && <span className="error">{errors.username}</span>} </div> <div> <label htmlFor="email">メールアドレス:</label> <input type="email" id="email" name="email" value={formData.email} onChange={handleChange} /> {errors.email && <span className="error">{errors.email}</span>} </div> <div> <label htmlFor="password">パスワード:</label> <input type="password" id="password" name="password" value={formData.password} onChange={handleChange} /> {errors.password && <span className="error">{errors.password}</span>} </div> <div> <label htmlFor="confirmPassword">パスワード(確認):</label> <input type="password" id="confirmPassword" name="confirmPassword" value={formData.confirmPassword} onChange={handleChange} /> {errors.confirmPassword && <span className="error">{errors.confirmPassword}</span>} </div> <button type="submit">登録</button> </form> ); }
Step 4他のフォーム要素
さまざまなフォーム要素の扱い方:
- セレクトボックス(ドロップダウン)
<select name="country" value={formData.country} onChange={handleChange} > <option value="">国を選択</option> <option value="japan">日本</option> <option value="usa">アメリカ</option> <option value="other">その他</option> </select>
- チェックボックス
const handleCheckboxChange = (event) => { const { name, checked } = event.target; setFormData({ ...formData, [name]: checked }); }; <label> <input type="checkbox" name="agreedToTerms" checked={formData.agreedToTerms} onChange={handleCheckboxChange} /> 利用規約に同意する </label>
- ラジオボタン
<div> <label>性別:</label> <label> <input type="radio" name="gender" value="male" checked={formData.gender === 'male'} onChange={handleChange} /> 男性 </label> <label> <input type="radio" name="gender" value="female" checked={formData.gender === 'female'} onChange={handleChange} /> 女性 </label> <label> <input type="radio" name="gender" value="other" checked={formData.gender === 'other'} onChange={handleChange} /> その他 </label> </div>
ポイント
ヒント: 大規模なフォームを扱う場合は、以下のライブラリの使用を検討してください:
- Formik - 制御されたコンポーネントのボイラープレートを減らす
- React Hook Form - パフォーマンスを重視した非制御コンポーネントベースのライブラリ
- Yup - フォームバリデーションのためのスキーマビルダー
まとめ
- Reactのフォームは
useStateで入力値を管理する制御コンポーネントが基本 onChangeイベントでstateを更新し、value属性でstateの値を表示する- 複数の入力欄は1つのオブジェクトstateでまとめて管理できる
onSubmitイベントで送信処理を行い、e.preventDefault()でページリロードを防ぐ- バリデーションはリアルタイム(onChange時)と送信時の両方で実装するのが望ましい
- 大規模なフォームにはReact Hook FormやFormikなどのライブラリの使用を検討する