Reactのフォーム処理:Controlled Components

2025-08-08

Reactにおけるフォーム処理は、HTMLフォームとは異なるアプローチが必要です。この章では、Reactの「Controlled Components(制御されたコンポーネント)」という概念を中心に、フォーム処理の基本から実践的な使い方までを詳細に解説します。

Controlled Componentsの基本概念

Controlled vs Uncontrolled

Reactのフォーム処理には2つのアプローチがあります。

  1. Controlled Components(制御されたコンポーネント)
  • Reactがフォームデータを完全に管理
  • 入力値は常にReactのstateと同期
  • 推奨される方法
  1. Uncontrolled Components(非制御コンポーネント)
  • 伝統的なHTMLフォームのようにDOMがデータを管理
  • refを使用して必要な時に値を取得

Controlled Componentsの仕組み

Controlled Componentsでは、以下のサイクルで動作します。

  1. フォーム入力が発生
  2. onChangeイベントハンドラーが呼び出される
  3. ハンドラーがstateを更新
  4. コンポーネントが再レンダリング
  5. 入力値が更新されたstateと同期

useStateで入力値(value)を状態として管理し、inputvalueにその状態を紐づけています。ユーザーが入力すると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)を状態として保持し、その値をinputvalueに設定しています。ユーザーが入力すると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)を状態として管理し、その値をtextareavalueに紐づけています。ユーザーが入力すると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)を状態として管理し、その値をselectvalueに紐づけています。初期値は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)として管理し、その値をinputcheckedに紐づけています。ユーザーがチェックを入れたり外したりすると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オブジェクトを使用する方法

useStateformDataというオブジェクトを持ち、name・email・ageをまとめて管理しています。各入力欄にはname属性を設定しており、handleChangeではe.targetからnamevalueを取り出し、該当するプロパティだけを更新します。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コンポーネントを作成してください。

  1. inputに文字を入力すると、その値をstateに保存すること
  2. onChangeイベントを使って入力値を取得すること
  3. 入力された文字数をリアルタイムで表示すること
  4. 入力欄は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

テキスト入力フォームを作成し、以下の条件を満たしてください。

  1. inputに入力された値をstateで管理すること(Controlled Componentにする)
  2. 入力された内容をリアルタイムで画面に表示すること
  3. inputの値は常にstateと同期していること

【完成イメージ】

入力欄: [ Hello ]
表示: Hello

問題11

以下の仕様を満たすReactコンポーネントを作成してください。

  1. チェックボックスの状態(チェックあり/なし)をstateで管理すること
  2. checkboxには適切なプロパティを使って状態を制御すること(valueではない)
  3. チェックされている場合は「同意しています」、されていない場合は「同意していません」と表示すること
  4. 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>
  );
}