Next.jsで学ぶReact講座(完全初心者向け・30日) | 第4章:実用的React – フォーム処理

React Next.js
スポンサーリンク

フォーム処理の全体イメージ

React / Next.js で「実用的なアプリ」を作ろうとすると、必ず出てくるのがフォームです。
ログインフォーム、問い合わせフォーム、検索フォーム、プロフィール編集画面…。
どれも本質的には「入力値を state で管理して、送信時にまとめて処理する」だけです。

ここでは、次の流れで整理していきます。

  • input の管理(「入力された値をどう持つか」)
  • submit 処理(「送信ボタンが押されたときに何をするか」)
  • 入力チェック(バリデーション)
  • よくある実装ミス

一つひとつ分解すれば、フォームはそこまで怖くありません。


input管理(入力値を state で持つ)

「入力欄=state」として考える

React では、フォームの入力値を「コンポーネントの state として持つ」のが基本です。
これを「制御されたコンポーネント(controlled component)」と呼びます。

シンプルな名前入力フォームを例にします。

"use client";

import { useState } from "react";

export default function NameForm() {
  const [name, setName] = useState("");

  return (
    <main style={{ padding: "24px" }}>
      <h1>名前フォーム</h1>
      <p>あなたの名前を入力してください。</p>

      <input
        type="text"
        value={name}
        onChange={(e) => setName(e.target.value)}
        style={{ padding: "8px", fontSize: "16px", width: "240px" }}
      />

      <p style={{ marginTop: "16px" }}>今の入力値: {name}</p>
    </main>
  );
}
TSX

ここで重要なのは次の 2 点です。

value={name}
入力欄の中身は、常に state name によって決まる。

onChange={(e) => setName(e.target.value)}
ユーザーが入力するたびに、setName で state を更新する。

つまり、

ユーザーがキーを打つ
onChange が発火する
state が更新される
再レンダリングされて、value={name} によって input に反映される

というループになっています。

「入力欄の中身は、常に state の値の“写し鏡”」というイメージを持つと分かりやすいです。

複数の input を管理する

フォームが少し大きくなると、入力欄が増えます。
例えば「名前+メールアドレス」のフォーム。

"use client";

import { useState } from "react";

export default function ContactForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  return (
    <main style={{ padding: "24px" }}>
      <h1>お問い合わせフォーム</h1>

      <div style={{ marginBottom: "12px" }}>
        <label>
          名前:
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
            style={{ marginLeft: "8px", padding: "4px 8px" }}
          />
        </label>
      </div>

      <div style={{ marginBottom: "12px" }}>
        <label>
          メールアドレス:
          <input
            type="email"
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            style={{ marginLeft: "8px", padding: "4px 8px" }}
          />
        </label>
      </div>

      <p>名前: {name}</p>
      <p>メール: {email}</p>
    </main>
  );
}
TSX

最初のうちは、こうやって「入力欄ごとに state を分ける」書き方で十分です。
慣れてきたら、オブジェクト 1 つにまとめるパターンもありますが、
初心者の段階では「1 input = 1 state」で考えたほうが混乱しにくいです。


submit処理(送信ボタンが押されたとき)

formタグと onSubmit を使う

フォームの送信処理は、<form> タグと onSubmit を使うのが基本です。

"use client";

import { useState } from "react";

export default function SimpleForm() {
  const [name, setName] = useState("");

  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault(); // ブラウザのデフォルト送信(ページリロード)を止める
    alert(`送信された名前: ${name}`);
  }

  return (
    <main style={{ padding: "24px" }}>
      <h1>フォーム送信の基本</h1>

      <form onSubmit={handleSubmit}>
        <label>
          名前:
          <input
            type="text"
            value={name}
            onChange={(e) => setName(e.target.value)}
            style={{ marginLeft: "8px" }}
          />
        </label>

        <div style={{ marginTop: "12px" }}>
          <button type="submit">送信</button>
        </div>
      </form>
    </main>
  );
}
TSX

ここで一番大事なのは e.preventDefault() です。

ブラウザのデフォルトのフォーム送信は、

  • ページをリロードして
  • action 先にデータを送る

という動きをします。
React / Next.js の SPA 的なフォームでは、ページリロードはしたくないので、
onSubmit の中で e.preventDefault() を呼んで「デフォルトの送信を止める」のが定番です。

その上で、

  • state に入っている値をまとめて使う(API に送る、アラートを出すなど)
  • 必要なら入力値をリセットする

という処理を書きます。

送信後に入力欄をクリアする

送信後にフォームを空にしたい場合は、state を初期値に戻します。

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  alert(`送信された名前: ${name}`);
  setName(""); // 入力欄を空に戻す
}
TSX

「フォームの中身を変えたいときは、input ではなく state を変える」
というルールはここでも同じです。


入力チェック(バリデーション)

まずは「必須チェック」から

入力チェック(バリデーション)は、いきなり完璧を目指さなくて大丈夫です。
最初は「必須項目が空じゃないか」を見るだけでも立派なバリデーションです。

"use client";

import { useState } from "react";

export default function ContactForm() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [error, setError] = useState<string | null>(null);

  function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();

    if (!name || !email) {
      setError("名前とメールアドレスは必須です。");
      return;
    }

    setError(null);
    alert(`送信しました!\n名前: ${name}\nメール: ${email}`);
    setName("");
    setEmail("");
  }

  return (
    <main style={{ padding: "24px" }}>
      <h1>お問い合わせフォーム</h1>

      <form onSubmit={handleSubmit}>
        <div style={{ marginBottom: "12px" }}>
          <label>
            名前:
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              style={{ marginLeft: "8px" }}
            />
          </label>
        </div>

        <div style={{ marginBottom: "12px" }}>
          <label>
            メールアドレス:
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              style={{ marginLeft: "8px" }}
            />
          </label>
        </div>

        {error && (
          <p style={{ color: "red", marginBottom: "12px" }}>{error}</p>
        )}

        <button type="submit">送信</button>
      </form>
    </main>
  );
}
TSX

ここでのポイントは、

  • error という state を用意して、エラーメッセージをそこに入れる
  • エラーがあるときだけエラーメッセージを表示する
  • エラーがなければ送信処理を続ける

という流れです。

もう少しだけ踏み込んだチェック(メール形式など)

簡単なメール形式チェックを入れることもできます。
正規表現を使うと本格的になりますが、最初は「@ が含まれているか」くらいでも十分です。

if (!email.includes("@")) {
  setError("メールアドレスの形式が正しくありません。");
  return;
}
TSX

大事なのは、「チェックに引っかかったらそこで処理を止める」ことです。
return で早めに抜けることで、
「エラーがあるのに送信処理が進んでしまう」ことを防げます。


よくある実装ミス

1. input に value だけ書いて onChange を忘れる

例えば、こんなコードです。

<input type="text" value={name} />
TSX

value だけ指定して onChange を書かないと、
入力欄が「読み取り専用」になってしまいます。

React は、「value を指定したら、それは制御されたコンポーネント」とみなすので、
ユーザーが入力しても state が変わらず、結果として見た目も変わりません。

必ず、

value={state}onChange={...} はセット

と覚えておきましょう。

2. form の onSubmit で preventDefault を忘れる

onSubmit の中で e.preventDefault() を呼び忘れると、
送信ボタンを押した瞬間にページがリロードされます。

React / Next.js の SPA 的なフォームでは、
「ページリロードせずに、JS の中で処理を完結させる」のが基本なので、
handleSubmit の最初の 1 行はほぼお約束でこうなります。

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
  // ここから自分の処理
}
TSX

3. 入力チェックをしているのに、エラーを画面に出していない

バリデーションをしていても、
エラーを console.log にだけ出して終わり、というパターンもよくあります。

ユーザーからすると、

  • 送信ボタンを押しても何も起きない
  • 何が悪いのか分からない

という状態になってしまいます。

必ず、

  • エラー内容を state に入れる
  • 画面上に「何が問題か」を表示する

という 2 ステップまでセットで考えましょう。

4. state を増やしすぎて自分で混乱する

フォームが大きくなると、
name, email, password, confirmPassword, age, address
と state がどんどん増えていきます。

最初のうちはそれでも構いませんが、
「どの state がどの input と対応しているか分からなくなってきた」と感じたら、
オブジェクト 1 つにまとめることも検討してよいです。

ただし、初心者の段階では、
まずは「1 input = 1 state」でフォームの流れに慣れるほうが大事です。
いきなり高度な抽象化をしようとすると、逆に分かりにくくなります。


まとめと練習の方向性

ここまでのポイントを整理すると、

フォームの入力値は、useState で管理し、valueonChange で「入力欄=state」の関係を作る。
送信処理は <form onSubmit={...}>e.preventDefault() を使い、state に入っている値をまとめて扱う。
入力チェックは、まずは「必須チェック」から始め、エラーは state に入れて画面に表示する。
よくあるミスは「value だけ指定して onChange を忘れる」「preventDefault を忘れてページがリロードされる」「エラーをユーザーに見せない」などで、ここを意識するだけで一気に実務寄りのフォームになる。

練習としては、

  • 名前+メール+メッセージの 3 項目フォーム
  • 必須チェック+メール形式チェック
  • 送信成功時に「ありがとうございます!」メッセージを表示

という小さな問い合わせフォームを、自分の手で一から書いてみるのがおすすめです。
フォームが一つ作れるようになると、「アプリを作っている感」が一気に増してきます。

タイトルとURLをコピーしました