0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

入力が消えるのはリロードされているからです — React の form で最初に踏む既定動作

0
Posted at

何が起きているか

React で入力フォームを作って、送信ボタンを押した瞬間に

  • 画面が一瞬白くなる(リロードされる)
  • 入力した値が全部消える
  • console.log に出したはずのログも消える

という現象があります。AI にコードを書いてもらった直後に気づきやすいので「AI が変なコードを書いた」と疑いたくなりますが、AI が的外れなものを書いたわけではありません。原因の場所が違うだけで、足りないのは1行です。

起きているのは、<form> の既定動作です。

なぜリロードされるのか

<form> は React が作った仕組みではなく、HTML にもともとある要素です。そして HTML のフォームには、送信されたら action 属性の URL にリクエストを送ってページを遷移する、という既定の動作があります。

action を書いていない場合、送信先は現在のドキュメントの URL になります。さらに method の既定値は GET なので、実際には現在のパスの末尾に ? と入力値が付いた URL が読み込み直されます

見分け方として使えます。送信ボタンを押したあと、アドレスバーの URL に ? が増えていたら、フォーム送信が起きたサインです。入力欄に name 属性が付いていれば ?name=taro のように入力値そのものが URL に出ます。

なお、もともと URL に付いていたクエリ文字列は、フォームの内容で置き換わります?case=4 を開いた状態で name="a" の欄に taro と入れて送信すると、URL は ?a=taro になります。

そして React のコンポーネントはページが読み込み直されると初期状態に戻るので、useState に入れていた入力値も一緒に消えます。「入力が消えた」の正体はこれです。

ℹ️ React 19 以降で <form action={関数}> のように action に関数を渡す場合(Server Actions など)は、React 側が既定動作を止めてくれるので preventDefault() は要りません。この記事は onSubmit で処理する従来の書き方の話です。

再現コード

"use client";

import { useState } from "react";

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

  const handleSubmit = () => {
    console.log("送信:", name); // ← 出るがすぐ消える
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button type="submit">送信</button>
    </form>
  );
}

これを動かすと、handleSubmitちゃんと呼ばれます。呼ばれたうえで、そのあとブラウザがフォームの既定動作を実行してページを再読み込みします。

「関数が呼ばれていないのでは」と疑って console.log を増やしていくと、ログが一瞬出て消えるので余計に混乱します。ここが引っかかりやすいところです。

💡 DevTools の Console で Preserve log にチェックを入れると、リロードをまたいでログが残ります。「呼ばれていない」のか「呼ばれたあとに消えている」のかを、これで切り分けられます。

直し方

イベントオブジェクトを受け取って、preventDefault() を呼びます。

import type { FormEvent } from "react";

const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
  e.preventDefault(); // ← 既定のページ遷移を止める
  console.log("送信:", name);
};

preventDefault() は「このイベントに紐づいたブラウザの既定動作をやらせない」という指示です。フォーム送信の場合は、ページ遷移がその既定動作にあたります。

これでリロードが止まり、name の値も残ります。

type="button" で消してはいけない理由

検索すると「<button type="button"> にすればリロードされない」という対処も出てきます。クリックしたときのリロードは、たしかに止まります。

{/* 症状は消えたように見えるが、副作用がある */}
<button type="button" onClick={handleClick}>送信</button>

ただしこれは、ボタンを押してもフォームを送信しない状態にしているだけです。次の2つが変わります。

1. ブラウザ標準のバリデーションが走らなくなる

クリック経路では送信そのものが起きないので、requiredtype="email" を付けても検証されないまま onClick の処理が動きます。「required を付けたのに未入力で通ってしまう」の原因がこれだったりします。

2. Enter キーの挙動が、テキスト入力欄の数によって変わる

ここが分かりにくいところです。HTML には**暗黙の送信(implicit submission)**という仕様があり、入力欄で Enter を押したときの挙動が次のように分かれます。

フォームの状態 Enter を押すと
submit ボタンがある そのボタンがクリックされる=送信される
submit ボタンが無く、テキスト入力欄が1つだけ フォームが送信される(=リロードが再発する)
submit ボタンが無く、テキスト入力欄が2つ以上 何も起きない

⚠️ ここで数えるのは <input type="text">type="email" などの1行テキスト入力だけです。<textarea><select>、チェックボックスは数に入りません。

これが地味に効きます。たとえば「名前(input)+ お問い合わせ内容(textarea)」という、いちばんありがちなフォーム。見た目は入力欄が2つですが、数えられるのは input の1つだけなので、上の表の2行目が適用されます。つまり名前欄で Enter を押すとリロードが再発します(textarea の中で押した Enter は改行になるだけなので、そちらで試すと何も起きません)。

つまり type="button" にすると、クリックでは直ったように見えるのに、Enter を押した人にだけ再発するという状態が作れてしまいます。これが見つけにくさの正体です。

どちらにしても、送信の意味を持たせたいなら type="submit" のまま onSubmitpreventDefault() するのが基本です。

ℹ️ ちなみに <button>type を省略すると submit 扱いになります。フォームの中で送信以外の用途にボタンを置くときは、明示的に type="button" を書く必要があります(今回とは逆に、書かないと送信されてしまいます)。

<a> タグでも同じことが起きます

preventDefault() はフォーム専用の話ではありません。既定動作を持つ要素すべてが対象です。

// クリックすると handleClick は動くが、そのあとページ最上部にジャンプする
<a href="#" onClick={handleClick}>詳細</a>

href="#" の既定動作は「文書の先頭へのスクロール」なので、位置が飛びます。加えて履歴に1件積まれるので、戻るボタンを押したときの挙動も変わります。ここでも e.preventDefault() が要りますが、そもそもページ遷移しないなら <button> を使うほうが適切です。

AI に聞くときの伝え方

この症状は「エラーが出ない」のが特徴です。コンソールは赤くならず、ただリロードされるだけなので、エラー文を貼って聞くことができません。

なので、症状をそのまま言葉にして渡します

Next.js (App Router) + React で入力フォームを作っています。
送信ボタンを押すと handleSubmit の console.log は一瞬出るのですが、
直後にページがリロードされて入力値が消えます。エラーは出ていません。

現在のコードは以下です。
(コードを貼る)

原因と、type="button" 以外の直し方を教えてください。

ポイントは2つです。

  1. 「エラーは出ていない」と明示する。書かないと、AI は存在しないエラーを前提に推測を広げがちです
  2. type="button" 以外で、と条件を付ける。これを書かないと、症状だけ消える対処が返ってくることがあります

まとめ

  • 送信でリロードされるのは React のバグではなく <form> の既定動作(method の既定が GET なので URL に ? が付く)
  • onSubmit の第1引数を受け取って e.preventDefault() を呼ぶ
  • type="button" は標準バリデーションを失わせ、Enter の挙動もテキスト入力欄の数で変わる(1つだけならリロードが再発する/textarea や select は数に入らない)
  • <a href="#"> でも同じ既定動作の問題が起きる(先頭スクロール+履歴が積まれる)
  • エラーが出ない症状は、症状の言葉と「エラーは出ていない」をセットで AI に渡す

この記事は初心者向けの内容です。中上級者の方には易しすぎる内容だと思いますので、プログラミングを始めたばかりの知り合いへの紹介や、社内研修の参考としてお使いいただければ嬉しいです。

未経験から Next.js + Supabase + Claude Code で Webアプリを公開するまでを教材にまとめています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?