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>
      )}
    </>
  );
};

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

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

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

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

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

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

buttonのtype="submit"を押すと、formのonSubmitが実行されます。

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では、400や500のレスポンスでも自動的には例外になりません。response.okを確認します。

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

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


関連記事

  • TypeScript i18nextの動的な翻訳キーを抽出対象に含める

    i18nextでステータスに応じた文言を表示する場合、翻訳キーを動的に切り替えることがあります。ただし、実行時に正しく表示できることと、翻訳キーの抽出ツールが使用箇所を検出できることは別です。今回は、...


  • React useStateで一覧の追加と削除を行う

    Reactで一覧を表示するときは、配列をuseStateへ保存できます。今回は簡単な買い物リストを作り、項目の追加と削除を行います。配列のpushやspliceで既存のstateを直接変更するのではな...


  • React useRefで入力欄にフォーカスを当てる

    入力フォームを開いた直後や、入力エラーが出たときに、特定の入力欄へフォーカスしたいことがあります。DOMの要素自体を参照するにはuseRefを使います。currentは初回描画中などにnullになり得...


  • React useMemoで重い計算結果を再利用する

    一覧の絞り込みや並べ替えを描画のたびに行うと、データ量や計算内容によっては画面操作が遅くなります。useMemoは、依存する値が変わらない間、以前の計算結果を再利用します。productsまたはque...


  • React useEffectで無限ループが発生するときに確認すること

    ReactのuseEffectを利用したときに無限ループが発生してしまうことがあります。特に注意したいのが、ESLintのreact-hooks/exhaustive-depsで表示された警告をUpd...


  • React useEffectの後片付けでイベント登録を解除する

    画面の幅が変わったときに表示を更新したい場合、ブラウザのresizeイベントを登録できます。登録したままにすると、コンポーネントが不要になったあとも処理が残るため、useEffectの後片付けを用意し...


  • React 関数コンポーネントにプロパティを設定する

    React+TypeScriptで作成した関数コンポーネントにプロパティを設定する方法を紹介します。以下は送信ボタンのコンポーネントを作成しています。isSubmittingというプロパティを用意して...


  • Reactで兄弟コンポーネントの状態を共有する

    検索欄に入力した文字列を、別の一覧コンポーネントでも使いたい場合があります。兄弟同士で別々にstateを持つと値がずれるため、共通の親にstateを置きます。入力欄は値を受け取り、変更を親へ通知します...


  • React useStateでselectの選択値を取得する

    Reactでプルダウンの選択値を扱うには、selectのvalueとonChangeを使用します。DOMから取得するvalueは、数値の選択肢を表示していても文字列です。選択値をそのまま識別子として扱...


  • React react-router-domの基本的な使い方

    React+TypeScriptのプロジェクトにreact-router-domを導入してSPAのアプリケーションを作成する方法を紹介します。まずはreact-router-domのパッケージをインス...