モダンJavaScriptの基礎から始める挫折しないためのReact入門 60日コース | フォーム・コンポーネント設計 - DAY 34:フォーム送信

React 60日で身につけるReact
スポンサーリンク
スポンサーリンク
  1. DAY 34:フォーム送信 前半 ― 「form+onSubmit+preventDefault」の基本を体に入れる
  2. フォーム送信の全体像を言葉でつかむ
    1. 「ボタンを押したら何が起きるか」を分解する
  3. formタグを使う意味を理解する
    1. 「ただのdiv」と「form」の違い
  4. onSubmitとpreventDefaultの役割をコードで確認する
    1. ユーザー登録フォームの骨組みを作る
  5. ユーザー登録フォームの入力値を送信処理で使う
    1. Stateから値を取り出して「送信された内容」を表示する
  6. DAY 34 前半版:ユーザー登録フォームテンプレート
  7. DAY 34 前半のまとめ ― 「form+onSubmit+preventDefault」が送信の土台
  8. DAY 34:フォーム送信 中盤 ― 「送信処理を設計する」という視点で form・onSubmit・preventDefault を深掘りする
  9. フォーム送信を「1つのイベント」として捉える
    1. onSubmitは「フォーム全体の出口」
  10. preventDefault を「送信処理の主導権をReact側に持ってくる一言」として理解する
    1. なぜ毎回 e.preventDefault() が必要なのか
  11. ユーザー登録フォームを「送信処理の設計図」として見る
    1. 入力欄を増やして「ユーザー情報」として扱う
  12. form・onSubmit・preventDefault を「役割ごとに分けて覚える」
    1. 3つのキーワードを自分の中で整理する
  13. DAY 34 中盤版テンプレート ― 「送信処理を意識したユーザー登録フォーム」
  14. DAY 34 中盤のまとめ ― 「送信処理を設計する」という視点を持つ
  15. DAY 34:フォーム送信 後半 ― 「実戦で使えるユーザー登録フォーム」に仕上げる
  16. ユーザー登録フォームのゴールを明確にする
    1. どんな情報を安全に扱いたいのか
  17. ユーザー登録フォームの完成形を先に見る
    1. 全体像(完成版コード)
  18. form・onSubmit・preventDefault の「後半での役割」
    1. form:送信の枠組み+バリデーションの入口
    2. onSubmit:送信処理の本体
    3. preventDefault:SPAとしてのフォーム送信の必須一言
  19. バリデーションとエラーメッセージを「送信処理の一部」として組み込む
    1. 必須チェック・形式チェック・範囲チェック
  20. セキュリティの入り口として意識しておきたいこと
    1. パスワードを「ログに出さない」という基本
  21. 送信中フラグとボタンの無効化
    1. 二重送信を防ぐための isSubmitting
  22. DAY 34 後半のまとめ ― 「実戦を意識したフォーム送信の型」を自分のものにする

DAY 34:フォーム送信 前半 ― 「form+onSubmit+preventDefault」の基本を体に入れる

DAY 34の前半では、 form・onSubmit・preventDefault を使った「フォーム送信の基本の流れ」を、 ユーザー登録フォームを題材にして身につけていきます。

ここでのゴールは、

  • なぜ form タグを使うのか
  • なぜ onSubmit を使うのか
  • なぜ preventDefault() が必要なのか

を、コードと動きの両方から理解することです。

フォーム送信の全体像を言葉でつかむ

「ボタンを押したら何が起きるか」を分解する

ブラウザの世界では、 <form> タグと送信ボタン(<button type="submit">)があると、 次のような流れが起きます。

  1. ユーザーが送信ボタンをクリックする
  2. ブラウザが「フォーム送信」のデフォルト動作を実行しようとする
    • ページをリロードする
    • action 属性に指定されたURLにデータを送る(HTMLの世界)
  3. Reactでは、その前に onSubmit に指定した関数が呼ばれる
  4. その関数の中で e.preventDefault() を呼ぶと、 ブラウザの「デフォルトのフォーム送信」を止めることができる

Reactアプリでは、 ページリロードせずに、JavaScriptの中でフォーム送信を処理したいので、 onSubmitpreventDefault() がほぼセットで登場します。

formタグを使う意味を理解する

「ただのdiv」と「form」の違い

まず、form を使わずにボタンを置いた場合を考えてみます。

function ExampleWithoutForm() {
  const handleClick = () => {
    alert("ボタンがクリックされました");
  };

  return (
    <div>
      <h2>フォーム送信の例(formなし)</h2>
      <input type="text" placeholder="何か入力してください" />
      <button onClick={handleClick}>送信</button>
    </div>
  );
}
JSX

この場合、

  • ボタンを押すと handleClick が呼ばれる
  • しかし「フォーム送信」という概念はありません
  • ブラウザの「フォーム送信のデフォルト動作」も発生しません

一方、form を使うとこうなります。

function ExampleWithForm() {
  const handleSubmit = (e) => {
    e.preventDefault();
    alert("フォームが送信されました(ページはリロードされません)");
  };

  return (
    <div>
      <h2>フォーム送信の例(formあり)</h2>
      <form onSubmit={handleSubmit}>
        <input type="text" placeholder="何か入力してください" />
        <button type="submit">送信</button>
      </form>
    </div>
  );
}
JSX

ここでは、

  • ボタンの type="submit" によって、「フォーム送信」が発生します
  • formonSubmit={handleSubmit} によって、handleSubmit が呼ばれます
  • e.preventDefault() によって、ページリロードが防がれます

ポイント:

Reactで「フォーム送信」を扱うときは、 formonSubmitpreventDefault() の3点セットが基本になります。

onSubmitとpreventDefaultの役割をコードで確認する

ユーザー登録フォームの骨組みを作る

ここからは、実際の「ユーザー登録フォーム」を作りながら、 form・onSubmit・preventDefault の流れを確認していきます。

まずは骨組みです。

import { useState } from "react";

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

  const handleSubmit = (e) => {
    e.preventDefault(); // ブラウザのデフォルトのフォーム送信(リロード)を止めます。
    alert("ユーザー登録フォームが送信されました");
  };

  return (
    <div>
      <h2>DAY 34:フォーム送信(前半)― ユーザー登録フォーム</h2>

      <form onSubmit={handleSubmit}>
        <div>
          <label>
            名前:
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="名前を入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            メール:
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="メールアドレスを入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "12px" }}>
          <button type="submit">登録</button>
        </div>
      </form>
    </div>
  );
}

export default UserRegisterForm;
JSX

ここで起きていることを、ステップで追いかけます。

  1. ユーザーが「登録」ボタンをクリックする
  2. button type="submit" によって、「フォーム送信」が発生する
  3. form onSubmit={handleSubmit} によって、handleSubmit が呼ばれる
  4. handleSubmit の中で e.preventDefault() を呼び、ページリロードを防ぐ
  5. その後、Reactの中で自由に送信処理(今回は alert)を書くことができる

重要な感覚:

onSubmit は「フォーム送信時に呼ばれる関数」であり、 preventDefault() は「ブラウザのデフォルト動作を止めるための一言」です。

ユーザー登録フォームの入力値を送信処理で使う

Stateから値を取り出して「送信された内容」を表示する

次に、handleSubmit の中で、 nameemail の値を使ってみます。

const handleSubmit = (e) => {
  e.preventDefault();

  alert(`ユーザー登録\n名前: ${name}\nメール: ${email}`);
};
JSX

これを先ほどのコンポーネントに組み込むと、 フォーム送信時に「入力された内容」が表示されるようになります。

ここでの流れはこうです。

  1. ユーザーが名前とメールを入力する
  2. onChange によって、nameemail のStateが更新される
  3. ユーザーが「登録」ボタンを押す
  4. handleSubmit が呼ばれ、preventDefault() でリロードを防ぐ
  5. nameemail の現在の値を使って、送信内容を処理する

ポイント:

onSubmit の中では、「フォーム全体の現在のState」を使って、 送信処理(表示・API送信・検証など)をまとめて行うことができます。

DAY 34 前半版:ユーザー登録フォームテンプレート

ここまでの内容をまとめた、 DAY 34前半の「ユーザー登録フォーム」のテンプレートを示します。

import { useState } from "react";

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

  const handleSubmit = (e) => {
    e.preventDefault(); // ページリロードを防ぎます。

    if (name.trim() === "" || email.trim() === "") {
      alert("名前とメールは必須です。");
      return;
    }

    alert(`ユーザー登録\n名前: ${name}\nメール: ${email}`);

    // 送信後にフォームをリセットする例
    setName("");
    setEmail("");
  };

  return (
    <div>
      <h2>DAY 34:フォーム送信(前半)― ユーザー登録フォーム</h2>

      <form onSubmit={handleSubmit}>
        <div>
          <label>
            名前:
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="名前を入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            メール:
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="メールアドレスを入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "12px" }}>
          <button type="submit">登録</button>
        </div>
      </form>
    </div>
  );
}

export default UserRegisterForm;
JSX

このテンプレートは、

  • form タグを使って「フォーム送信」という枠組みを作り
  • onSubmit で送信時の処理をまとめ
  • preventDefault() でページリロードを防ぎ
  • Stateから入力値を取り出して送信内容を扱う

という、 Reactでフォーム送信を扱うときの基本形をすべて含んでいます。

DAY 34 前半のまとめ ― 「form+onSubmit+preventDefault」が送信の土台

DAY 34前半で、自分の中にしっかり残してほしいのは次のポイントです。

  • Reactでフォーム送信を扱うときは、 formタグ+onSubmit+preventDefault() の3点セットが基本になること。
  • form を使うことで、「送信ボタンを押すとフォーム送信が発生する」という枠組みができること。
  • onSubmit の中では、 ページリロードを防ぐために e.preventDefault() を呼び、 その後でStateから入力値を取り出して送信処理を行うことができること。
  • ユーザー登録フォームのようなシンプルな例でも、 この流れを意識して書くことで、 「フォーム送信の仕組み」がはっきり見えてくること。

この「form+onSubmit+preventDefault」の型が体に馴染むと、 この先のフォーム送信(複数項目・バリデーション・API連携・エラー処理など)が、 すべてこの基本形の上に自然に積み上げられるようになっていきます。


DAY 34:フォーム送信 中盤 ― 「送信処理を設計する」という視点で form・onSubmit・preventDefault を深掘りする

DAY 34の中盤では、前半で触れた

  • form
  • onSubmit
  • preventDefault

を、「送信処理を設計する」という視点からもう一段深く理解していきます。

題材は引き続き「ユーザー登録フォーム」ですが、 単に送信するだけでなく、

  • 送信前に何をチェックするか
  • 送信時にどんなデータを作るか
  • 送信後にフォームをどう扱うか

といった「実際のアプリで必要になる設計」を意識して見ていきます。

フォーム送信を「1つのイベント」として捉える

onSubmitは「フォーム全体の出口」

まず、フォーム送信の流れを整理します。

<form onSubmit={handleSubmit}>
  {/* 入力欄いろいろ */}
  <button type="submit">登録</button>
</form>
JSX

ここで起きていることは、

  1. ユーザーが「登録」ボタンをクリックする
  2. button type="submit" によって、「フォーム送信イベント」が発生する
  3. form onSubmit={handleSubmit} によって、handleSubmit が呼ばれる
  4. handleSubmit の中で、フォーム全体の処理をまとめて行う

重要なポイント:

onSubmit は「フォーム全体の出口」であり、 ここで「送信してよいか」「何を送るか」「送信後にどうするか」を決める。

この感覚があると、 フォーム送信が「ただのボタンクリック」ではなく、 「フォーム全体の状態を扱うイベント」として見えてきます。

preventDefault を「送信処理の主導権をReact側に持ってくる一言」として理解する

なぜ毎回 e.preventDefault() が必要なのか

handleSubmit の中では、ほぼ必ずこう書きます。

const handleSubmit = (e) => {
  e.preventDefault();
  // ここから先はReact側で送信処理をコントロールします
};
JSX

これは、

  • ブラウザの「デフォルトのフォーム送信」(ページリロード+HTML的な送信)を止めて
  • 「送信処理の主導権をReact側に持ってくる」

ための一言です。

もし preventDefault() を書かないと、

  • ページがリロードされてしまう
  • ReactのStateが消えてしまう
  • SPA(シングルページアプリ)の流れが崩れてしまう

といった問題が起きます。

重要な感覚:

preventDefault() は、 「ブラウザに任せず、送信処理を自分(React)のコードで制御するためのスイッチ」である。

ユーザー登録フォームを「送信処理の設計図」として見る

入力欄を増やして「ユーザー情報」として扱う

中盤では、ユーザー登録フォームを少しだけ拡張してみます。

  • 名前
  • メール
  • パスワード
  • 年齢

を持つフォームを作り、 送信時に「ユーザー情報オブジェクト」を組み立てる形にします。

import { useState } from "react";

function UserRegisterFormMid() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [age, setAge] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();

    // 1. 必須チェック(簡易版)
    if (
      name.trim() === "" ||
      email.trim() === "" ||
      password.trim() === ""
    ) {
      alert("名前・メール・パスワードは必須です。");
      return;
    }

    // 2. 年齢の扱い(任意+数値変換)
    const hasAge = age.trim() !== "";
    const ageNumber = hasAge ? Number(age) : null;

    // 3. 送信するデータをオブジェクトとしてまとめる
    const userData = {
      name,
      email,
      password,
      age: ageNumber,
    };

    // 4. 送信内容を確認(本来はAPI送信など)
    console.log("送信するユーザー情報:", userData);
    alert("ユーザー登録処理を実行しました(コンソールに内容を出力しています)。");

    // 5. フォームをリセットする
    setName("");
    setEmail("");
    setPassword("");
    setAge("");
  };

  return (
    <div>
      <h2>DAY 34:フォーム送信(中盤)― ユーザー登録フォーム(設計版)</h2>

      <form onSubmit={handleSubmit}>
        <div>
          <label>
            名前:
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="名前を入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            メール:
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="メールアドレスを入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            パスワード:
            <input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="パスワードを入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            年齢(任意):
            <input
              type="number"
              value={age}
              onChange={(e) => setAge(e.target.value)}
              placeholder="年齢を入力してください(任意)"
            />
          </label>
        </div>

        <div style={{ marginTop: "12px" }}>
          <button type="submit">登録</button>
        </div>
      </form>
    </div>
  );
}

export default UserRegisterFormMid;
JSX

ここでの流れを、設計目線で整理します。

  1. 入力フェーズ
    • 各入力欄がControlled ComponentとしてStateと同期しています。
  2. 送信イベント
    • 「登録」ボタンを押すと、formonSubmit が発火し、handleSubmit が呼ばれます。
  3. デフォルト動作の停止
    • e.preventDefault() によって、ページリロードを防ぎます。
  4. バリデーション(必須チェック)
    • 名前・メール・パスワードが空なら、送信処理を止めて警告します。
  5. データ整形
    • 年齢を数値に変換し、任意項目として扱います。
  6. 送信データの構築
    • userData オブジェクトとして、ユーザー情報を1つにまとめます。
  7. 送信処理(ここでは console.log+alert)
    • 実際のアプリでは、この部分がAPI呼び出しなどに置き換わります。
  8. フォームリセット
    • 送信後にStateを初期化し、フォームを空に戻します。

重要な感覚:

onSubmit の中では、 「チェック → 整形 → 送信 → リセット」という一連の流れを 1つの関数の中で設計していく。

form・onSubmit・preventDefault を「役割ごとに分けて覚える」

3つのキーワードを自分の中で整理する

ここで、3つのキーワードを役割ごとに整理しておきます。

form

  • 役割: 「フォーム送信」という枠組みを作る
  • イメージ: 入力欄と送信ボタンをまとめる「フォームの箱」
  • ポイント: button type="submit" とセットで使うことで、「送信イベント」が発生する

onSubmit

  • 役割: フォーム送信時に呼ばれる関数を指定する
  • イメージ: 「フォーム全体の出口」
  • ポイント: バリデーション・データ整形・送信処理・リセットなどをまとめて書く場所

preventDefault

  • 役割: ブラウザのデフォルトのフォーム送信(リロードなど)を止める
  • イメージ: 「主導権をReact側に持ってくるスイッチ」
  • ポイント: SPAとして動かすためにほぼ必須の一言

この3つをセットで覚えておくと、 どんなフォームでも「まずこの形から書き始める」という自分なりの型ができます。

DAY 34 中盤版テンプレート ― 「送信処理を意識したユーザー登録フォーム」

最後に、DAY 34中盤の内容をすべて含んだテンプレートをまとめます。

import { useState } from "react";

function UserRegisterFormTemplate() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [age, setAge] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();

    // 必須チェック
    if (
      name.trim() === "" ||
      email.trim() === "" ||
      password.trim() === ""
    ) {
      alert("名前・メール・パスワードは必須です。");
      return;
    }

    // 年齢の扱い
    const hasAge = age.trim() !== "";
    const ageNumber = hasAge ? Number(age) : null;

    // 送信データの構築
    const userData = {
      name,
      email,
      password,
      age: ageNumber,
    };

    // 送信処理(ここではログ+アラート)
    console.log("送信するユーザー情報:", userData);
    alert("ユーザー登録処理を実行しました。");

    // フォームリセット
    setName("");
    setEmail("");
    setPassword("");
    setAge("");
  };

  return (
    <div>
      <h2>DAY 34:フォーム送信(中盤)― ユーザー登録フォームテンプレート</h2>

      <form onSubmit={handleSubmit}>
        <div>
          <label>
            名前:
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="名前を入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            メール:
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="メールアドレスを入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            パスワード:
            <input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="パスワードを入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            年齢(任意):
            <input
              type="number"
              value={age}
              onChange={(e) => setAge(e.target.value)}
              placeholder="年齢を入力してください(任意)"
            />
          </label>
        </div>

        <div style={{ marginTop: "12px" }}>
          <button type="submit">登録</button>
        </div>
      </form>
    </div>
  );
}

export default UserRegisterFormTemplate;
JSX

DAY 34 中盤のまとめ ― 「送信処理を設計する」という視点を持つ

DAY 34中盤で、自分の中に定着させてほしいのは次のポイントです。

  • form+onSubmit+preventDefault は、 「フォーム送信の枠組み+出口+主導権のスイッチ」という3つの役割を持っていること。
  • onSubmit の中では、 「チェック → 整形 → 送信 → リセット」という一連の流れを 1つの関数として設計していくことができること。
  • ユーザー登録フォームのような例でも、 送信時に「ユーザー情報オブジェクト」を組み立てることで、 実際のAPI送信にそのままつなげられる形になること。
  • このテンプレートを「フォーム送信の出発点」として持っておくと、 どんなフォームでも、まずこの形から落ち着いて書き始められるようになること。

この「送信処理を設計する」という視点が身につくと、 この先のフォーム設計(バリデーション強化、エラー表示、API連携、成功/失敗メッセージなど)が、 すべてこの基本形の延長として、自然に組み立てられるようになっていきます。


DAY 34:フォーム送信 後半 ― 「実戦で使えるユーザー登録フォーム」に仕上げる

DAY 34の後半では、 これまで扱ってきた

  • form
  • onSubmit
  • preventDefault

を使って作る「ユーザー登録フォーム」を、実戦で使えるレベルに近づけていきます。

ここでは特に、

  • フォーム送信時のバリデーション
  • セキュリティの入り口として意識しておきたいポイント
  • 実際のAPI連携を意識した「送信処理の形」

を、ステップバイステップでかみ砕いて説明していきます。

ユーザー登録フォームのゴールを明確にする

どんな情報を安全に扱いたいのか

ユーザー登録フォームでは、一般的に次のような情報を扱います。

  • 名前(表示名)
  • メールアドレス(ログインID・連絡先)
  • パスワード(認証情報)
  • 任意のプロフィール情報(年齢など)

後半では、このうち

  • 名前
  • メール
  • パスワード
  • 年齢(任意)

を扱うフォームを題材にします。

ここで意識してほしいこと:

パスワードは「特に慎重に扱うべき情報」であり、 フロントエンド側でも「むやみに表示しない」「ログに出さない」などの基本的な配慮が必要です。

ユーザー登録フォームの完成形を先に見る

全体像(完成版コード)

まず、完成形のコードを示します。 このあとで、重要な部分を細かく分解して説明していきます。

import { useState } from "react";

function UserRegisterFormFinal() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [age, setAge] = useState("");

  const [error, setError] = useState("");
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault(); // ブラウザのデフォルト送信(リロード)を止めます。

    setError("");

    // 1. 必須チェック
    if (
      name.trim() === "" ||
      email.trim() === "" ||
      password.trim() === ""
    ) {
      setError("名前・メール・パスワードはすべて必須です。");
      return;
    }

    // 2. メール形式の簡易チェック
    if (!email.includes("@")) {
      setError("メールアドレスの形式が正しくありません。");
      return;
    }

    // 3. パスワードの簡易チェック(長さ)
    if (password.length < 8) {
      setError("パスワードは8文字以上にしてください。");
      return;
    }

    // 4. 年齢の扱い(任意+範囲チェック)
    const hasAge = age.trim() !== "";
    const ageNumber = hasAge ? Number(age) : null;

    if (hasAge && (Number.isNaN(ageNumber) || ageNumber < 0 || ageNumber > 120)) {
      setError("年齢は0〜120の範囲で入力してください。");
      return;
    }

    // 5. 送信データの構築(パスワードはログに出さない)
    const userData = {
      name,
      email,
      password, // 実際にはサーバー側でハッシュ化されるべき情報です。
      age: ageNumber,
    };

    try {
      setIsSubmitting(true);

      // 6. API送信を想定した処理(ダミー)
      // 実際には fetch や axios などでサーバーに送信します。
      console.log("送信するユーザー情報(パスワードはログに出さないのが理想です)");
      console.log({
        name: userData.name,
        email: userData.email,
        age: userData.age,
      });

      alert("ユーザー登録が完了しました。");

      // 7. フォームリセット
      setName("");
      setEmail("");
      setPassword("");
      setAge("");
    } catch (err) {
      setError("ユーザー登録中にエラーが発生しました。もう一度お試しください。");
    } finally {
      setIsSubmitting(false);
    }
  };

  return (
    <div>
      <h2>DAY 34:フォーム送信(後半)― ユーザー登録フォーム完成版</h2>

      <form onSubmit={handleSubmit}>
        <div>
          <label>
            名前:
            <input
              type="text"
              value={name}
              onChange={(e) => setName(e.target.value)}
              placeholder="名前を入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            メール:
            <input
              type="email"
              value={email}
              onChange={(e) => setEmail(e.target.value)}
              placeholder="メールアドレスを入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            パスワード:
            <input
              type="password"
              value={password}
              onChange={(e) => setPassword(e.target.value)}
              placeholder="パスワードを入力してください"
            />
          </label>
        </div>

        <div style={{ marginTop: "8px" }}>
          <label>
            年齢(任意):
            <input
              type="number"
              value={age}
              onChange={(e) => setAge(e.target.value)}
              placeholder="年齢を入力してください(任意)"
            />
          </label>
        </div>

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

        <div style={{ marginTop: "12px" }}>
          <button type="submit" disabled={isSubmitting}>
            {isSubmitting ? "登録中..." : "登録"}
          </button>
        </div>
      </form>
    </div>
  );
}

export default UserRegisterFormFinal;
JSX

ここから、重要な部分を順番に分解していきます。

form・onSubmit・preventDefault の「後半での役割」

form:送信の枠組み+バリデーションの入口

<form onSubmit={handleSubmit}> は、 「フォーム全体の送信イベントの入口」です。

  • 入力欄と送信ボタンをひとまとめにする「箱」
  • button type="submit" と組み合わせて、「送信イベント」を発生させる
  • 送信時に handleSubmit が必ず呼ばれる

Reactでは、 この form を使うことで「送信処理を1箇所に集約する」ことができます。

onSubmit:送信処理の本体

onSubmit={handleSubmit} によって、 フォーム送信時に handleSubmit が呼ばれます。

後半では、この handleSubmit の中に

  1. preventDefault()
  2. バリデーション
  3. データ構築
  4. API送信(想定)
  5. フォームリセット
  6. エラー処理・送信中フラグ

をすべてまとめています。

重要なポイント:

onSubmitの中身は「送信処理の本体」であり、 ここを丁寧に設計することで、フォームの品質が決まります。

preventDefault:SPAとしてのフォーム送信の必須一言

e.preventDefault(); は、 ブラウザのデフォルトのフォーム送信(ページリロード)を止めるための一言です。

これを入れることで、

  • ReactのStateが消えない
  • SPAとしての画面遷移が保たれる
  • 送信処理を完全にJavaScript側で制御できる

という状態になります。

後半での意識:

「まず最初に preventDefault() を書く」 という癖をつけておくと、フォーム送信で変な挙動に悩まされにくくなります。

バリデーションとエラーメッセージを「送信処理の一部」として組み込む

必須チェック・形式チェック・範囲チェック

後半では、次のようなバリデーションを入れています。

// 必須チェック
if (
  name.trim() === "" ||
  email.trim() === "" ||
  password.trim() === ""
) {
  setError("名前・メール・パスワードはすべて必須です。");
  return;
}

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

// パスワードの簡易チェック
if (password.length < 8) {
  setError("パスワードは8文字以上にしてください。");
  return;
}

// 年齢の範囲チェック
const hasAge = age.trim() !== "";
const ageNumber = hasAge ? Number(age) : null;

if (hasAge && (Number.isNaN(ageNumber) || ageNumber < 0 || ageNumber > 120)) {
  setError("年齢は0〜120の範囲で入力してください。");
  return;
}
JSX

ここでのポイントは、

  • 送信前に「送信してよい状態か」を必ず確認していること
  • 条件を満たさない場合は return で処理を止めていること
  • エラーメッセージを setError でStateに保存し、画面に表示していること

です。

画面側では、次のように表示しています。

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

重要な感覚:

バリデーションとエラーメッセージは、 「送信処理の一部」として onSubmit の中に組み込む。

セキュリティの入り口として意識しておきたいこと

パスワードを「ログに出さない」という基本

後半のコードでは、 送信データ userData を作りつつ、 console.log にはパスワードを含めない形を意識しています。

const userData = {
  name,
  email,
  password, // 実際にはサーバー側でハッシュ化されるべき情報です。
  age: ageNumber,
};

console.log("送信するユーザー情報(パスワードはログに出さないのが理想です)");
console.log({
  name: userData.name,
  email: userData.email,
  age: userData.age,
});
JSX

ここで意識してほしいのは、

  • パスワードは「特に慎重に扱うべき情報」であること
  • フロントエンド側でも、ログや画面にパスワードを表示しないこと
  • 実際のシステムでは、サーバー側でハッシュ化して保存すること(フロント側では生パスワードを保持しない時間を短くする)

という基本的な姿勢です。

重要な感覚:

「パスワードを扱うときは、ログに出さない・むやみに表示しない」 という意識を、フォーム設計の段階から持っておく。

送信中フラグとボタンの無効化

二重送信を防ぐための isSubmitting

後半のコードでは、 isSubmitting というStateを使って「送信中かどうか」を管理しています。

const [isSubmitting, setIsSubmitting] = useState(false);

try {
  setIsSubmitting(true);
  // 送信処理(API想定)
  alert("ユーザー登録が完了しました。");
  // フォームリセット...
} catch (err) {
  setError("ユーザー登録中にエラーが発生しました。もう一度お試しください。");
} finally {
  setIsSubmitting(false);
}
JSX

ボタン側では、次のように使っています。

<button type="submit" disabled={isSubmitting}>
  {isSubmitting ? "登録中..." : "登録"}
</button>
JSX

これにより、

  • 送信中はボタンが押せなくなる
  • ユーザーに「今処理中である」ことが伝わる
  • 二重送信を防ぐことができる

という効果があります。

重要な感覚:

「送信中フラグ」を持つことで、 ユーザー体験と安全性(重複送信防止)が両方よくなる。

DAY 34 後半のまとめ ― 「実戦を意識したフォーム送信の型」を自分のものにする

DAY 34後半で、自分の中に定着させてほしいポイントは次の通りです。

  • form+onSubmit+preventDefault は、 「フォーム送信の枠組み+出口+主導権のスイッチ」として、実戦でもそのまま使える基本形であること。
  • onSubmit の中では、 バリデーション → データ構築 → 送信処理 → フォームリセット → エラー処理 を1つの流れとして設計できること。
  • ユーザー登録フォームのような場面では、 パスワードをログに出さない・むやみに表示しないといった セキュリティの基本的な意識を、フロントエンド側でも持つことが大切であること。
  • 送信中フラグ(isSubmitting)を使うことで、 二重送信防止とユーザー体験の向上を両立できること。

この「実戦を意識したフォーム送信の型」が身につくと、 この先のフォーム設計(API連携、エラー表示の細分化、成功/失敗メッセージ、認証フローなど)が、 すべてこの基本形の延長として、落ち着いて組み立てられるようになっていきます。

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