zukucode
主にWEB関連の情報を技術メモとして発信しています。

React useStateで入力フォームを作成する

Reactでテキストボックスに入力した値を使用するには、useStateで値を保持します。

今回は名前とメールアドレスを入力し、送信ボタンを押すと入力内容を表示するフォームを作成します。

この例では入力値をReactのstateに置く「制御されたコンポーネント」を使用します。入力のたびに検証や表示を変えたい小さなフォームでは扱いやすい一方、項目数が多く性能や検証管理が問題になる場合は、フォームライブラリや非制御コンポーネントも選択肢になります。

1つの入力値を保持する

最初に名前だけを入力できるコンポーネントを作成します。

NameInput.tsx
import { useState } from 'react';

export const NameInput = () => {
  const [name, setName] = useState('');

  return (
    <div>
      <label htmlFor="name">名前</label>
      <input
        id="name"
        type="text"
        value={name}
        onChange={(event) => setName(event.target.value)}
      />
      <p>入力中の名前: {name}</p>
    </div>
  );
};

useState('')は、初期値が空文字のstateを作成します。

nameには現在の値、setNameには値を変更する関数が入ります。

テキストボックスの内容が変わるたびにonChangeが実行され、event.target.valueをstateへ保存します。

複数の入力値をオブジェクトで保持する

入力項目が増えた場合は、フォーム全体を1つのオブジェクトで保持できます。

ContactForm.tsx
import { useState, type ChangeEvent, type FormEvent } from 'react';

type FormData = {
  name: string;
  email: string;
};

const initialFormData: FormData = {
  name: '',
  email: '',
};

export const ContactForm = () => {
  const [formData, setFormData] = useState(initialFormData);
  const [submittedData, setSubmittedData] = useState<FormData>();

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = event.target;

    setFormData((current) => ({
      ...current,
      [name]: value,
    }));
  };

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setSubmittedData(formData);
  };

  return (
    <>
      <form onSubmit={handleSubmit}>
        <div>
          <label htmlFor="name">名前</label>
          <input
            id="name"
            name="name"
            value={formData.name}
            onChange={handleChange}
          />
        </div>

        <div>
          <label htmlFor="email">メールアドレス</label>
          <input
            id="email"
            name="email"
            type="email"
            value={formData.email}
            onChange={handleChange}
          />
        </div>

        <button type="submit">送信</button>
      </form>

      {submittedData && (
        <p>
          {submittedData.name}さんのメールアドレスは
          {submittedData.email}です。
        </p>
      )}
    </>
  );
};

inputnameを、FormDataのプロパティ名と同じにしています。

setFormDataでは、スプレッド構文の...currentで現在の値をコピーしてから、変更された項目だけを上書きします。

preventDefaultで画面の再読み込みを防ぐ

HTMLのフォームは、送信すると通常はページを再読み込みします。

React内で送信処理を行う場合は、event.preventDefault()で標準の送信動作を止めます。

const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
  event.preventDefault();
  setSubmittedData(formData);
};

buttontype="submit"を押すと、formonSubmitが実行されます。

Enterキーで送信した場合も同じ処理を通るため、ボタンのonClickだけに送信処理を書くより扱いやすくなります。

必須入力を確認する

簡単な必須チェックなら、HTMLのrequired属性を使用できます。

<input
  id="name"
  name="name"
  value={formData.name}
  onChange={handleChange}
  required
/>

空のまま送信すると、ブラウザが入力を求めるメッセージを表示します。

複雑な条件やサーバーから返されたエラーを表示する場合は、エラーメッセージ用のstateやフォームライブラリを使用します。

APIへ送信する

入力内容をAPIへ送る場合は、送信処理をasyncにします。

APIへ送信する例
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
  event.preventDefault();

  const response = await fetch('/api/contacts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(formData),
  });

  if (!response.ok) {
    setMessage('送信に失敗しました。');
    return;
  }

  setMessage('送信しました。');
  setFormData(initialFormData);
};

fetchでは、400500のレスポンスでも自動的には例外になりません。response.okを確認します。

APIへ送信する場合は送信中のstateも用意し、処理が終わるまで送信ボタンを無効にします。

まずはvalueonChangeで入力値をstateへ保存し、onSubmitでまとめて処理する流れを覚えると、Reactのフォームを作りやすくなります。


関連記事