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

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

この課題のねらい

ここでは「フォーム入力をちゃんと React で扱う」ことがテーマです。
キーワードは、state・onChange・onSubmit・バリデーション(入力チェック)。
最終的に、「ユーザーが入力 → state に反映 → 送信ボタンで処理 → 入力がおかしければエラー表示」という一連の流れを、自分で組み立てられるようになるのがゴールです。


フォーム入力をstate管理する

単一入力フォームの基本パターン

まずは、名前を入力するだけの超シンプルなフォームから始めます。
ポイントは「input の値を state で管理する(=制御されたコンポーネントにする)」ことです。

"use client";

import { useState } from "react";

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

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setName(event.target.value);
  }

  return (
    <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
      <h1>フォーム入力の基本</h1>

      <form>
        <label>
          名前:
          <input
            type="text"
            value={name}
            onChange={handleChange}
            style={{ marginLeft: "8px" }}
          />
        </label>

        <p style={{ marginTop: "12px" }}>今入力されている名前:{name}</p>
      </form>
    </main>
  );
}
TSX

ここで大事なのは次の関係です。
input の value に state(name)を渡しているので、「表示されている値の正体は state」。
onChange で入力イベントを受け取り、event.target.value を setName に渡して state を更新している。
ユーザーがタイプするたびに state が更新され、その値がまた input に反映される、というループになっている。

これが「フォーム入力を state 管理する」の基本形です。
この形をベースに、項目を増やしたり、送信処理を足したりしていきます。


submit処理を実装する

フォーム送信時に処理を走らせる

次は、「送信ボタンを押したときに何かする」パターンです。
React では form に onSubmit を付けて、イベントの中で処理を書きます。

"use client";

import { useState } from "react";

export default function SubmitFormPage() {
  const [name, setName] = useState("");
  const [submittedName, setSubmittedName] = useState<string | null>(null);

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setName(event.target.value);
  }

  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault(); // ブラウザのデフォルト送信(ページリロード)を止める
    setSubmittedName(name);
  }

  return (
    <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
      <h1>フォーム送信の例</h1>

      <form onSubmit={handleSubmit}>
        <label>
          名前:
          <input
            type="text"
            value={name}
            onChange={handleChange}
            style={{ marginLeft: "8px" }}
          />
        </label>

        <button
          type="submit"
          style={{
            marginLeft: "12px",
            padding: "4px 12px",
            borderRadius: "9999px",
            border: "none",
            backgroundColor: "#3b82f6",
            color: "white",
            cursor: "pointer",
          }}
        >
          送信
        </button>
      </form>

      {submittedName && (
        <p style={{ marginTop: "16px" }}>送信された名前:{submittedName}</p>
      )}
    </main>
  );
}
TSX

ここでの流れを整理します。
ユーザーが入力するたびに name state が更新される。
送信ボタンを押すと form の onSubmit が発火し、handleSubmit が呼ばれる。
handleSubmit の中で event.preventDefault() を呼び、ブラウザの標準のフォーム送信(ページリロード)を止める。
その上で、今の name を submittedName にコピーし、「送信された値」として画面に表示している。

「入力中の値」と「送信済みの値」を分けて state 管理しているのがポイントです。
実務でも、「入力中のフォーム」と「サーバーに送った値」を分けて扱うことはよくあります。


入力チェック(バリデーション)を追加する

まずは「空文字禁止」のシンプルなチェック

挑戦課題は「入力チェック追加」です。
最初はシンプルに、「名前が空のまま送信しようとしたらエラーにする」パターンから始めましょう。

"use client";

import { useState } from "react";

export default function ValidatedFormPage() {
  const [name, setName] = useState("");
  const [submittedName, setSubmittedName] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    setName(event.target.value);
    if (error) {
      setError(null); // 入力し直したらエラーを消す
    }
  }

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

    if (name.trim() === "") {
      setError("名前を入力してください。");
      setSubmittedName(null);
      return;
    }

    setSubmittedName(name.trim());
    setError(null);
  }

  return (
    <main style={{ padding: "24px", fontFamily: "sans-serif" }}>
      <h1>入力チェック付きフォーム</h1>

      <form onSubmit={handleSubmit}>
        <label>
          名前:
          <input
            type="text"
            value={name}
            onChange={handleChange}
            style={{ marginLeft: "8px" }}
          />
        </label>

        <button
          type="submit"
          style={{
            marginLeft: "12px",
            padding: "4px 12px",
            borderRadius: "9999px",
            border: "none",
            backgroundColor: "#3b82f6",
            color: "white",
            cursor: "pointer",
          }}
        >
          送信
        </button>
      </form>

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

      {submittedName && !error && (
        <p style={{ marginTop: "12px" }}>送信された名前:{submittedName}</p>
      )}
    </main>
  );
}
TSX

ここでの重要ポイントはこうです。
error という state を用意し、「エラーメッセージを入れる場所」を作っている。
handleSubmit の中で name.trim() が空かどうかをチェックし、空なら error にメッセージをセットして return している(送信処理を中断)。
エラーがあるときは赤文字でメッセージを表示し、エラーがないときだけ送信結果を表示している。
handleChange の中で「入力し直したらエラーを消す」ことで、ユーザーが修正したときにちゃんとエラーが消えるようにしている。

これだけで、「雑に送信させない」「何がダメかを教える」という最低限の UX が成立します。


もう一歩:複数項目のフォームをイメージする

ここまで理解できたら、次は「項目が増えたとき」をイメージしてみてください。
例えば、「名前」と「メールアドレス」の 2 項目フォームなら、こんな感じになります。

state を name と email の 2 つにする。
エラーも nameError と emailError に分けるか、オブジェクトでまとめる。
handleSubmit の中で、それぞれの項目に対してチェックを行い、どこがダメかを個別に表示する。

実務では、これをもう少し抽象化して「フォーム用のカスタムフック」や「フォームライブラリ(react-hook-form など)」を使いますが、
まずは「state で入力を持つ」「onChange で更新する」「onSubmit でチェックして処理する」という素の流れを自分の手で書けることが大事です。


まとめ:フォームでつかんでほしいこと

この課題で押さえてほしいのは次の感覚です。
input の value を state に結びつけて、「表示されている値の正体は state」にする。
onChange で入力イベントを受け取り、event.target.value を使って state を更新する。
form の onSubmit で event.preventDefault() を呼び、React 側で送信処理をコントロールする。
送信前に入力チェックを行い、ダメなときはエラーメッセージを state で管理して画面に表示する。

ここが腑に落ちると、ログインフォーム、問い合わせフォーム、プロフィール編集画面など、
「ユーザーが何かを入力して送信する画面」を自分の頭で設計できるようになっていきます。

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