1. ホーム
  2. React

【React】制御コンポーネントでフォーム入力を扱う方法|value と onChange で状態管理

Share

React でフォームを作るとき、入力欄の値をどう扱えばよいか迷うことはないでしょうか。React の定番は制御コンポーネント(controlled component)という考え方で、入力欄の値を useState の状態と結びつけて管理します。こうすると、入力内容をリアルタイムで検証したり、送信時にまとめて扱ったりが簡単になります。この記事では、制御コンポーネントの基本、複数入力欄のまとめ方、チェックボックスやセレクトボックスの扱い、そしてフォーム送信までを、初心者向けに具体的なコードで解説します。

制御コンポーネントとは

通常の HTML の入力欄は、値をブラウザ(DOM)が自分で持っています。一方 React の制御コンポーネントでは、入力欄の値を React の状態(state)が「正」として管理し、入力欄にはその状態を value として渡します。ユーザーが文字を打つたびに onChange で状態を更新し、更新された状態が再び入力欄に反映される、という一方向の流れになります。

controlled-input.tsx
import { useState } from 'react';

function NameForm() {
  const [name, setName] = useState('');

  return (
    <div>
      <input
        type="text"
        value={name}                       // 状態を値として渡す
        onChange={(e) => setName(e.target.value)} // 入力のたびに状態を更新
      />
      <p>こんにちは、{name} さん</p>
    </div>
  );
}

ポイントは value={name}onChange のペアです。ユーザーがキーを押すと onChange が発火し、e.target.value(入力された文字列)で状態 name を更新します。状態が変わると再レンダリングされ、value={name} によって入力欄に最新の値が表示されます。状態が常に入力欄の内容と一致しているため、下の <p> のように入力値をそのまま画面の別の場所で使うのも簡単です。

複数の入力欄を1つの状態でまとめる

入力欄が増えるたびに useState を増やすのは大変です。関連する項目は、1つのオブジェクトの状態にまとめると管理しやすくなります。各入力欄に name 属性を付けておき、onChange で「どの項目が変わったか」を見て更新します。

multi-input.tsx
import { useState } from 'react';

function ProfileForm() {
  const [form, setForm] = useState({ username: '', email: '' });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    // 変わった項目だけを上書きする
    setForm((prev) => ({ ...prev, [name]: value }));
  };

  return (
    <form>
      <input name="username" value={form.username} onChange={handleChange} />
      <input name="email" value={form.email} onChange={handleChange} />
    </form>
  );
}

handleChange はどの入力欄からでも共通で使えます。e.target.name で変更された項目名('username''email')を取り出し、[name]: value という計算されたプロパティ名でその項目だけを上書きします。...prev で既存の値を引き継いでいるので、他の項目は保持されます。入力欄が増えても handleChange を使い回せるのが利点です。

チェックボックス・セレクトボックスの扱い

テキスト以外の入力欄も、基本は「状態と結びつける」考え方は同じですが、値の取り出し方が少し違います。チェックボックスは value ではなく checked(真偽値)を使い、<select> は選択中の value を状態と結びつけます。

checkbox-select.tsx
import { useState } from 'react';

function OptionsForm() {
  const [agreed, setAgreed] = useState(false);
  const [plan, setPlan] = useState('free');

  return (
    <div>
      {/* チェックボックスは checked と e.target.checked */}
      <label>
        <input
          type="checkbox"
          checked={agreed}
          onChange={(e) => setAgreed(e.target.checked)}
        />
        規約に同意する
      </label>

      {/* select は value を状態と結びつける */}
      <select value={plan} onChange={(e) => setPlan(e.target.value)}>
        <option value="free">無料プラン</option>
        <option value="pro">プロプラン</option>
      </select>
    </div>
  );
}

チェックボックスはオン・オフを表すので、状態は真偽値にして checked={agreed}e.target.checked を使います。<select> は、HTML のように <option>selected を付けるのではなく、<select> 側の value で選択中の項目を決めるのが React 流です。どの入力部品でも「状態を value(または checked)で渡し、変更を onChange で状態に反映する」という形は共通しています。

フォームを送信する

入力値を状態で持っているので、送信処理はとても簡単です。<form>onSubmit で状態をそのまま使えます。忘れてはいけないのが e.preventDefault() です。これを呼ばないと、ブラウザ本来のフォーム送信でページが再読み込みされてしまいます。

submit-form.tsx
import { useState } from 'react';

function LoginForm() {
  const [form, setForm] = useState({ email: '', password: '' });

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault(); // ページの再読み込みを止める
    console.log('送信内容:', form);
    // ここで API へ送信するなどの処理を書く
  };

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setForm((prev) => ({ ...prev, [name]: value }));
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="email" value={form.email} onChange={handleChange} />
      <input name="password" type="password" value={form.password} onChange={handleChange} />
      <button type="submit">ログイン</button>
    </form>
  );
}

onSubmit の中では、状態 form に入力内容がすべて揃っているので、そのまま API 送信などに使えます。制御コンポーネントの大きなメリットはここにあります。いつでも最新の入力内容が状態として手元にあるため、送信・検証・整形といった処理を状態を見るだけで書けるのです。

入力しても文字が表示されないとき

制御コンポーネントで初心者がよくハマるのが、「value は指定したのに、入力しても文字が出ない」という症状です。原因は、value を渡しているのに onChange を書き忘れている(または状態を更新していない)ことです。

制御コンポーネントでは、表示される値はあくまで状態です。onChange で状態を更新しなければ、キーを打っても状態は変わらず、value は初期値のまま固定されてしまいます。valueonChange は必ずセットで書く、と覚えておきましょう。なお「値は変えないが読み取り専用で表示したい」場合は、onChange の代わりに readOnly を付ければ警告も出ません。

まとめ

制御コンポーネントは、入力欄の値を React の状態で管理する方法です。基本の形は value={状態}onChange のペアで、ユーザーの入力を onChange で状態に反映し、その状態を value として表示します。複数の入力欄は name 属性とオブジェクトの状態でまとめると、共通の handleChange で扱えます。チェックボックスは checked<select><select> 側の value を使う点だけ押さえておきましょう。送信は onSubmite.preventDefault() を呼び、状態をそのまま利用します。「入力欄の値は常に状態が正」という一方向の流れを理解すれば、検証や送信を含むフォームを見通しよく組み立てられます。

参考ページ