Reactのフォーム処理:Controlled Components
2025-08-08Reactにおけるフォーム処理は、HTMLフォームとは異なるアプローチが必要です。この章では、Reactの「Controlled Components(制御されたコンポーネント)」という概念を中心に、フォーム処理の基本から実践的な使い方までを詳細に解説します。
Controlled Componentsの基本概念
Controlled vs Uncontrolled
Reactのフォーム処理には2つのアプローチがあります。
- Controlled Components(制御されたコンポーネント)
- Reactがフォームデータを完全に管理
- 入力値は常にReactのstateと同期
- 推奨される方法
- Uncontrolled Components(非制御コンポーネント)
- 伝統的なHTMLフォームのようにDOMがデータを管理
- refを使用して必要な時に値を取得
Controlled Componentsの仕組み
Controlled Componentsでは、以下のサイクルで動作します。
- フォーム入力が発生
- onChangeイベントハンドラーが呼び出される
- ハンドラーがstateを更新
- コンポーネントが再レンダリング
- 入力値が更新されたstateと同期
useStateで入力値(value)を状態として管理し、inputのvalueにその状態を紐づけています。ユーザーが入力するとonChangeイベントが発火し、handleChange内でevent.target.valueを取得してstateを更新するため、入力値とstateが常に同期されます。
import { useState } from 'react';
function ControlledForm() {
const [value, setValue] = useState('');
const handleChange = (event) => {
setValue(event.target.value);
};
const handleSubmit = (event) => {
event.preventDefault();
alert(`入力値: ${value}`);
};
return (
<form onSubmit={handleSubmit}>
<label>
名前:
<input type="text" value={value} onChange={handleChange} />
</label>
<button type="submit">送信</button>
</form>
);
}
フォーム送信時にはhandleSubmitが実行され、event.preventDefault()でページの再読み込みを防ぎつつ、現在の入力値をアラートで表示します。
基本的なフォーム要素の扱い方
テキスト入力(input type=”text”)
useStateで入力内容(text)を状態として保持し、その値をinputのvalueに設定しています。ユーザーが入力するとonChangeイベントが発火し、アロー関数内でe.target.valueを取得してsetTextで状態を更新します。
function TextInput() {
const [text, setText] = useState('');
return (
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="テキストを入力"
/>
);
}
これにより、入力欄の表示とstateが常に一致する仕組みになっています。また、placeholderは入力欄が空のときに表示される補助テキストです。
テキストエリア
useStateでテキスト内容(text)を状態として管理し、その値をtextareaのvalueに紐づけています。ユーザーが入力するとonChangeが発火し、e.target.valueを使ってsetTextで状態を更新するため、入力内容とstateが常に同期されます。
function TextAreaInput() {
const [text, setText] = useState('');
return (
<textarea
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="複数行のテキストを入力"
/>
);
}
inputと基本的な仕組みは同じで、違いは複数行入力ができるtextareaを使っている点です。また、placeholderは未入力時に表示される案内文です。
セレクトボックス
useStateで選択中の値(selectedOption)を状態として管理し、その値をselectのvalueに紐づけています。初期値はoption1です。ユーザーが別の選択肢を選ぶとonChangeが発火し、e.target.valueを使って状態を更新するため、選択内容とstateが常に同期されます。
function SelectBox() {
const [selectedOption, setSelectedOption] = useState('option1');
return (
<select
value={selectedOption}
onChange={(e) => setSelectedOption(e.target.value)}
>
<option value="option1">オプション1</option>
<option value="option2">オプション2</option>
<option value="option3">オプション3</option>
</select>
);
}
つまり、「現在どのオプションが選ばれているか」をReact側で管理している実装です。
チェックボックス
useStateでチェック状態(isChecked)を真偽値(true/false)として管理し、その値をinputのcheckedに紐づけています。ユーザーがチェックを入れたり外したりするとonChangeが発火し、e.target.checkedを使って状態を更新するため、チェック状態とstateが常に同期されます。
function CheckboxExample() {
const [isChecked, setIsChecked] = useState(false);
return (
<label>
<input
type="checkbox"
checked={isChecked}
onChange={(e) => setIsChecked(e.target.checked)}
/>
同意する
</label>
);
}
valueではなくcheckedを使う点が、テキスト入力との違いです。
ラジオボタン
useStateで現在選択されている値(selectedOption)を管理し、その値と各ラジオボタンのvalueを比較して、checkedの状態を決めています(selectedOption === 'option1'など)。ユーザーが選択を変更するとonChangeが発火し、e.target.valueで選択された値を取得してstateを更新します。
function RadioButtons() {
const [selectedOption, setSelectedOption] = useState('option1');
return (
<div>
<label>
<input
type="radio"
value="option1"
checked={selectedOption === 'option1'}
onChange={(e) => setSelectedOption(e.target.value)}
/>
オプション1
</label>
<label>
<input
type="radio"
value="option2"
checked={selectedOption === 'option2'}
onChange={(e) => setSelectedOption(e.target.value)}
/>
オプション2
</label>
</div>
);
}
ラジオボタンは複数の中から1つだけ選択するため、同じstateを使って「どれが選ばれているか」を判定している点が特徴です。
複数入力フォームの管理
個別のstateを使用する方法
useStateを使って「名前・メールアドレス・年齢」をそれぞれ個別のstateとして管理しています。各入力欄はvalueにstateを紐づけ、onChangeで入力内容を取得して対応するstateを更新することで、すべての入力値がReact側で管理されます。
function MultiInputForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
console.log({ name, email, age });
};
return (
<form onSubmit={handleSubmit}>
<label>
名前:
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</label>
<label>
メールアドレス:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
年齢:
<input
type="number"
value={age}
onChange={(e) => setAge(e.target.value)}
/>
</label>
<button type="submit">送信</button>
</form>
);
}
フォーム送信時にはhandleSubmitが実行され、e.preventDefault()でページ遷移を防ぎつつ、入力されたデータをまとめてオブジェクトとしてconsole.logに出力します。
単一のstateオブジェクトを使用する方法
useStateでformDataというオブジェクトを持ち、name・email・ageをまとめて管理しています。各入力欄にはname属性を設定しており、handleChangeではe.targetからnameとvalueを取り出し、該当するプロパティだけを更新します。setFormDataではスプレッド構文(...prev)を使って既存のデータを保持しつつ、変更された項目のみ上書きしています。
function UnifiedStateForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
age: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<label>
名前:
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
/>
</label>
<label>
メールアドレス:
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
</label>
<label>
年齢:
<input
type="number"
name="age"
value={formData.age}
onChange={handleChange}
/>
</label>
<button type="submit">送信</button>
</form>
);
}
フォーム送信時にはhandleSubmitが実行され、ページ遷移を防いだ上で、現在の入力内容(formData)をまとめて出力します。
フォームバリデーション
フォームのバリデーション(入力チェック)とは、Webフォームなどでユーザーが入力したデータが、あらかじめ決められたルールや条件(必須、桁数、形式など)を満たしているかを検証する処理です。
基本的なバリデーション
formDataでメールアドレスとパスワードの入力値を管理し、errorsでエラーメッセージを管理しています。handleChangeでは入力内容をstateに反映し、常に入力値とstateが同期される仕組みです。
validate関数では入力内容をチェックし、未入力や形式不正(メール形式・文字数不足など)があればエラーメッセージをerrorsに設定します。最終的にエラーがなければtrueを返します。
function ValidatedForm() {
const [formData, setFormData] = useState({
email: '',
password: ''
});
const [errors, setErrors] = useState({});
const validate = () => {
const newErrors = {};
if (!formData.email) {
newErrors.email = 'メールアドレスは必須です';
} else if (!/^\S+@\S+\.\S+$/.test(formData.email)) {
newErrors.email = '有効なメールアドレスを入力してください';
}
if (!formData.password) {
newErrors.password = 'パスワードは必須です';
} else if (formData.password.length < 8) {
newErrors.password = 'パスワードは8文字以上必要です';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
console.log('フォームが送信されました:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>メールアドレス:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <span style={{ color: 'red' }}>{errors.email}</span>}
</div>
<div>
<label>パスワード:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <span style={{ color: 'red' }}>{errors.password}</span>}
</div>
<button type="submit">ログイン</button>
</form>
);
}
フォーム送信時(handleSubmit)にこのvalidateを実行し、問題がなければデータを送信(ここではconsole出力)します。エラーがある場合は、該当箇所の下に赤文字でメッセージが表示されます。
リアルタイムバリデーション
formDataで入力値(ユーザー名・メール)を管理し、errorsでエラーメッセージ、touchedで「一度でも触れたかどうか」を管理しています。
validateField関数は、項目ごとに入力内容をチェックし、条件に応じてエラーメッセージを返します。handleChangeでは入力値を更新すると同時に、すでに触れられている項目(touchedがtrue)の場合のみリアルタイムでバリデーションを実行します。
function RealTimeValidation() {
const [formData, setFormData] = useState({
username: '',
email: ''
});
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const validateField = (name, value) => {
let error = '';
if (name === 'username') {
if (!value) error = 'ユーザー名は必須です';
else if (value.length < 3) error = '3文字以上必要です';
}
if (name === 'email') {
if (!value) error = 'メールアドレスは必須です';
else if (!/^\S+@\S+\.\S+$/.test(value)) error = '有効なメールアドレスを入力してください';
}
return error;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
if (touched[name]) {
setErrors(prev => ({
...prev,
[name]: validateField(name, value)
}));
}
};
const handleBlur = (e) => {
const { name, value } = e.target;
setTouched(prev => ({ ...prev, [name]: true }));
setErrors(prev => ({
...prev,
[name]: validateField(name, value)
}));
};
const handleSubmit = (e) => {
e.preventDefault();
// 全てのフィールドを検証
const newErrors = {};
Object.keys(formData).forEach(name => {
newErrors[name] = validateField(name, formData[name]);
});
setErrors(newErrors);
// エラーがない場合のみ送信
if (Object.values(newErrors).every(error => !error)) {
console.log('フォームが送信されました:', formData);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>ユーザー名:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
onBlur={handleBlur}
/>
{errors.username && <span style={{ color: 'red' }}>{errors.username}</span>}
</div>
<div>
<label>メールアドレス:</label>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
onBlur={handleBlur}
/>
{errors.email && <span style={{ color: 'red' }}>{errors.email}</span>}
</div>
<button type="submit">登録</button>
</form>
);
}
handleBlurは入力欄からフォーカスが外れたときに呼ばれ、その項目を「触れた」と記録し、初回のバリデーションを実行します。
送信時(handleSubmit)には、すべての項目をまとめて検証し、エラーがなければデータを送信(ここではconsole出力)します。エラーがある場合は各入力欄の下に表示されます。
フォーム処理のベストプラクティス
カスタムフックの作成
複数のフォームで同じロジックを再利用するために、カスタムフックを作成できます。
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({
...prev,
[name]: value
}));
if (touched[name]) {
setErrors(prev => ({
...prev,
[name]: validate[name](value)
}));
}
};
const handleBlur = (e) => {
const { name, value } = e.target;
setTouched(prev => ({ ...prev, [name]: true }));
setErrors(prev => ({
...prev,
[name]: validate[name](value)
}));
};
return {
values,
errors,
touched,
handleChange,
handleBlur,
setValues,
setErrors,
setTouched
};
}
// 使用例
function UserForm() {
const { values, errors, touched, handleChange, handleBlur } = useForm(
{ username: '', email: '' },
{
username: (value) => {
if (!value) return 'ユーザー名は必須です';
if (value.length < 3) return '3文字以上必要です';
return '';
},
email: (value) => {
if (!value) return 'メールアドレスは必須です';
if (!/^\S+@\S+\.\S+$/.test(value)) return '有効なメールアドレスを入力してください';
return '';
}
}
);
const handleSubmit = (e) => {
e.preventDefault();
// フォーム送信処理
};
return (
<form onSubmit={handleSubmit}>
{/* フォームフィールド */}
</form>
);
}
パフォーマンス最適化
大きなフォームでは、各入力ごとにコンポーネント全体が再レンダリングされるのを防ぐために、個々の入力フィールドをメモ化できます。
const MemoizedInput = React.memo(function Input({ label, name, value, error, touched, onChange, onBlur }) {
return (
<div>
<label>{label}</label>
<input
type="text"
name={name}
value={value}
onChange={onChange}
onBlur={onBlur}
/>
{touched && error && <span style={{ color: 'red' }}>{error}</span>}
</div>
);
});
function OptimizedForm() {
// ...フォームロジック...
return (
<form onSubmit={handleSubmit}>
<MemoizedInput
label="ユーザー名"
name="username"
value={values.username}
error={errors.username}
touched={touched.username}
onChange={handleChange}
onBlur={handleBlur}
/>
{/* 他のフィールド */}
</form>
);
}
サードパーティライブラリの利用
大規模なアプリケーションでは、フォーム処理ライブラリを使用すると便利です。
Formikの基本的な使用例
import { Formik, Form, Field, ErrorMessage } from 'formik';
function FormikForm() {
return (
<Formik
initialValues={{ email: '', password: '' }}
validate={values => {
const errors = {};
if (!values.email) {
errors.email = '必須項目です';
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
) {
errors.email = '無効なメールアドレスです';
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({ isSubmitting }) => (
<Form>
<label htmlFor="email">メールアドレス</label>
<Field type="email" name="email" />
<ErrorMessage name="email" component="div" />
<label htmlFor="password">パスワード</label>
<Field type="password" name="password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={isSubmitting}>
送信
</button>
</Form>
)}
</Formik>
);
}
React Hook Formの基本的な使用例
import { useForm } from 'react-hook-form';
function HookForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label>ユーザー名</label>
<input
{...register("username", { required: true, minLength: 3 })}
/>
{errors.username?.type === "required" && (
<p>ユーザー名は必須です</p>
)}
{errors.username?.type === "minLength" && (
<p>3文字以上入力してください</p>
)}
<label>メールアドレス</label>
<input
type="email"
{...register("email", { required: true, pattern: /^\\S+@\\S+\\.\\S+$/ })}
/>
{errors.email && <p>有効なメールアドレスを入力してください</p>}
<button type="submit">登録</button>
</form>
);
}
よくある問題と解決策
パフォーマンス問題
問題:大きなフォームで入力が遅れる
解決策:
- デバウンス処理を実装
- コンポーネントをメモ化
- 必要なフィールドのみをレンダリング
function DebouncedInput({ value, onChange, ...props }) {
const [internalValue, setInternalValue] = useState(value);
const timerRef = useRef();
useEffect(() => {
setInternalValue(value);
}, [value]);
const handleChange = (e) => {
const newValue = e.target.value;
setInternalValue(newValue);
clearTimeout(timerRef.current);
timerRef.current = setTimeout(() => {
onChange(newValue);
}, 300);
};
return <input {...props} value={internalValue} onChange={handleChange} />;
}
複雑なバリデーション
問題:複数のフィールドに依存するバリデーション
解決策:フォーム全体のバリデーション関数を使用
function validateForm(values) {
const errors = {};
if (!values.password) {
errors.password = 'パスワードは必須です';
}
if (!values.confirmPassword) {
errors.confirmPassword = '確認用パスワードは必須です';
} else if (values.password !== values.confirmPassword) {
errors.confirmPassword = 'パスワードが一致しません';
}
return errors;
}
動的フォーム
問題:フィールド数が動的に変化する
解決策:配列ベースのstate管理
function DynamicForm() {
const [fields, setFields] = useState([{ id: 1, value: '' }]);
const addField = () => {
setFields([...fields, { id: Date.now(), value: '' }]);
};
const removeField = (id) => {
setFields(fields.filter(field => field.id !== id));
};
const handleChange = (id, value) => {
setFields(fields.map(field =>
field.id === id ? { ...field, value } : field
));
};
return (
<div>
{fields.map(field => (
<div key={field.id}>
<input
value={field.value}
onChange={(e) => handleChange(field.id, e.target.value)}
/>
<button onClick={() => removeField(field.id)}>削除</button>
</div>
))}
<button onClick={addField}>フィールドを追加</button>
</div>
);
}
まとめ:Reactフォーム処理の重要なポイント
Reactにおけるフォーム管理では、Controlled Componentsの考え方が重要です。これは、フォームの入力データをReact側が完全に管理する仕組みであり、入力値は常にstateと同期されます。これにより、入力内容を一元的に制御できるようになります。
フォームでは、input、textarea、select、checkbox、radioといった基本的な要素を使用し、それぞれに適切なvalueプロパティとイベントハンドラーを設定する必要があります。これにより、ユーザーの入力を正しく取得し、状態として管理できます。
また、バリデーションについては、入力中に即座にチェックを行うリアルタイムバリデーションと、送信時にまとめて確認する包括的なバリデーションの両方を適切に使い分けることが重要です。さらに、ユーザーにとって分かりやすいエラーメッセージを表示することで、使いやすさを向上させることができます。
パフォーマンスの観点では、メモ化やデバウンス処理を活用し、不要な再レンダリングを防ぐ工夫が求められます。必要な部分だけを効率よく更新することが、アプリケーション全体のパフォーマンス向上につながります。
さらに、大規模なアプリケーションでは、カスタムフックを作成してロジックを再利用しやすくしたり、FormikやReact Hook Formといった専用ライブラリを活用することで、フォーム管理をより効率的かつ保守しやすくすることができます。
Reactのフォーム処理は初めは複雑に感じるかもしれませんが、Controlled Componentsの概念を理解すれば、強力で柔軟なフォームを作成できます。実践を重ねることで、より洗練されたフォーム処理ができるようになります。
演習問題
初級問題(3問)
問題1
ユーザー名が3文字未満の場合、入力中にエラーを表示してください。
// 要件
// ・入力中にチェック
// ・3文字未満ならエラー表示function App() {
return (
<div>
{/* ここに実装 */}
</div>
);
}
問題2
入力値をstateで管理し、画面にリアルタイムで表示するコンポーネントを作成してください。
// 要件
// ・inputに入力した文字をstateで管理する
// ・入力内容を<p>タグに表示するfunction App() {
return (
<div>
{/* ここに実装 */}
</div>
);
}
問題3
3つ以上の入力フィールドを持つフォームを作成し、それぞれ個別stateで管理してください。
// 要件
// ・name, email, ageを個別useStateで管理
// ・すべて表示function App() {
return (
<div>
{/* ここに実装 */}
</div>
);
}
中級問題(6問)
問題4
以下の仕様を満たすReactコンポーネントを作成してください。
- inputに文字を入力すると、その値をstateに保存すること
- onChangeイベントを使って入力値を取得すること
- 入力された文字数をリアルタイムで表示すること
- 入力欄はControlled Componentとして実装すること
【完成イメージ】
入力欄: [ Hello ]
文字数: 5
問題5
名前とメールアドレスを1つのstateオブジェクトで管理するフォームを作成してください。
// 要件
// ・name, emailをformDataで管理
// ・入力値をconsole.logで出力function App() {
return (
<form>
{/* ここに実装 */}
</form>
);
}
問題6
1つのhandleChange関数で複数inputを更新できるフォームを作成してください。
// 要件
// ・inputにname属性を設定
// ・handleChangeでどの入力か判別してstate更新function App() {
return (
<div>
{/* ここに実装 */}
</div>
);
}
問題7
メールが未入力の場合にエラーを表示するフォームを作成してください。
// 要件
// ・未入力なら「必須です」と表示
// ・送信ボタン押下でチェックfunction App() {
return (
<form>
{/* ここに実装 */}
</form>
);
}
問題8
2つの選択肢から1つだけ選択できるラジオボタンを作成してください。
// 要件
// ・選択された値をstateで管理
// ・現在の選択を<p>に表示function App() {
return (
<div>
{/* ここに実装 */}
</div>
);
}
問題9
フォーム送信時にページリロードを防ぎつつ、入力値を表示してください。
// 要件
// ・submit時にalertで値表示
// ・ページ遷移を防ぐfunction App() {
return (
<form>
{/* ここに実装 */}
</form>
);
}
問題10
テキスト入力フォームを作成し、以下の条件を満たしてください。
- inputに入力された値をstateで管理すること(Controlled Componentにする)
- 入力された内容をリアルタイムで画面に表示すること
- inputの値は常にstateと同期していること
【完成イメージ】
入力欄: [ Hello ]
表示: Hello
問題11
以下の仕様を満たすReactコンポーネントを作成してください。
- チェックボックスの状態(チェックあり/なし)をstateで管理すること
- checkboxには適切なプロパティを使って状態を制御すること(valueではない)
- チェックされている場合は「同意しています」、されていない場合は「同意していません」と表示すること
- Controlled Componentとして実装すること
【完成イメージ】
[☑] 利用規約に同意する
表示: 同意しています
問題12
ControlledとUncontrolledの両方のinputを1つのコンポーネントで作成してください。
// 要件
// ・1つはstateで管理(Controlled)
// ・もう1つはrefで取得(Uncontrolled)function App() {
return (
<div>
{/* ここに実装 */}
</div>
);
}
演習問題解答例
初級問題(3問)
問題1解答例
import { useState } from 'react';
function App() {
const [name, setName] = useState('');
return (
<div>
<input
value={name}
onChange={(e) => setName(e.target.value)}
/>
{name.length < 3 && <p>3文字以上必要</p>}
</div>
);
}
問題2解答例
import { useState } from 'react';
function App() {
const [text, setText] = useState('');
return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
<p>{text}</p>
</div>
);
}
問題3解答例
import { useState } from 'react';
function App() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [age, setAge] = useState('');
return (
<div>
<input onChange={(e) => setName(e.target.value)} />
<input onChange={(e) => setEmail(e.target.value)} />
<input onChange={(e) => setAge(e.target.value)} />
<p>{name} {email} {age}</p>
</div>
);
}
問題4解答例
import React, { useState } from 'react';
function TextLengthCounter() {
const [text, setText] = useState('');
const handleChange = (e) => {
setText(e.target.value);
};
return (
<div>
<input
type="text"
value={text}
onChange={handleChange}
/>
<p>文字数: {text.length}</p>
</div>
);
}
export default TextLengthCounter;
問題5解答例
import { useState } from 'react';
function App() {
const [formData, setFormData] = useState({
name: '',
email: ''
});
const handleSubmit = (e) => {
e.preventDefault();
console.log(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="name"
onChange={(e) =>
setFormData({ ...formData, name: e.target.value })
}
/>
<input
name="email"
onChange={(e) =>
setFormData({ ...formData, email: e.target.value })
}
/>
<button>送信</button>
</form>
);
}
問題6解答例
import { useState } from 'react';
function App() {
const [data, setData] = useState({
name: '',
email: ''
});
const handleChange = (e) => {
const { name, value } = e.target;
setData(prev => ({ ...prev, [name]: value }));
};
return (
<div>
<input name="name" onChange={handleChange} />
<input name="email" onChange={handleChange} />
</div>
);
}
問題7解答例
import { useState } from 'react';
function App() {
const [email, setEmail] = useState('');
const [error, setError] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!email) {
setError('必須です');
} else {
setError('');
}
};
return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{error && <p>{error}</p>}
<button>送信</button>
</form>
);
}
問題8解答例
import { useState } from 'react';
function App() {
const [value, setValue] = useState('a');
return (
<div>
<input
type="radio"
value="a"
checked={value === 'a'}
onChange={(e) => setValue(e.target.value)}
/>A
<input
type="radio"
value="b"
checked={value === 'b'}
onChange={(e) => setValue(e.target.value)}
/>B
<p>{value}</p>
</div>
);
}
問題9解答例
import { useState } from 'react';
function App() {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
alert(text);
};
return (
<form onSubmit={handleSubmit}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
<button>送信</button>
</form>
);
}
問題10解答例
import React, { useState } from 'react';
function ControlledInput() {
const [text, setText] = useState('');
const handleChange = (e) => {
setText(e.target.value);
};
return (
<div>
<input
type="text"
value={text}
onChange={handleChange}
/>
<p>表示: {text}</p>
</div>
);
}
export default ControlledInput;
問題11解答例
import React, { useState } from 'react';
function CheckboxExample() {
const [isChecked, setIsChecked] = useState(false);
const handleChange = (e) => {
setIsChecked(e.target.checked);
};
return (
<div>
<label>
<input
type="checkbox"
checked={isChecked}
onChange={handleChange}
/>
利用規約に同意する
</label>
<p>
表示: {isChecked ? '同意しています' : '同意していません'}
</p>
</div>
);
}
export default CheckboxExample;
問題12解答例
import { useState, useRef } from 'react';
function App() {
const [text, setText] = useState('');
const inputRef = useRef();
const handleClick = () => {
alert(inputRef.current.value);
};
return (
<div>
{/* Controlled */}
<input
value={text}
onChange={(e) => setText(e.target.value)}
/>
{/* Uncontrolled */}
<input ref={inputRef} />
<button onClick={handleClick}>取得</button>
</div>
);
}