👨‍🏫

Next.js v15からはフォーム内容がリセットされる

2025/01/21に公開

最近いろんなプロジェクトをNext.js v14からv15にアップデート作業している@zaruです、こんにちは。

Next.js v15の大きな変更点はリリースノートなどで発表されていますし、アップグレードをサポートする codemod もあり、大半のプロジェクトは npx @next/codemod@canary upgrade latest を実行するだけで自動で修正されます。

しかし、Next.js v15のリリースノートには書かれていない(と思う)のですが、Next.js v15にはフォームをサブミットすると、フォーム内容がリセットされるように変更されています。これはv14とは異なる挙動です。

「フォームをサブミットすると、フォーム内容がリセットされる」とテキストで書くと、それはそうでしょう。なに当たり前のこと言ってるんだ?となるかもしれません。

実際に挙動を確認したほうが早いので以下のコードと画像を見てください。

サンプルコード

page.tsx
import { submit } from "@/app/action"; // 適当なServerAction

export default function Page() {
  return (
    <form action={submit}>
      <input type="text" name="name" />
      <button type="submit">送信</button>
    </form>
  );
}

v14の挙動

v15の挙動

画像の通り、v14では入力した内容がフォームサブミット後に残っているのに、v15では綺麗にリレットされています。

これは不具合ではない

僕がv15を検証中にこの差分に気がついたとき「不具合かなぁ」と思って対応待ちしてたんですが、ReactのIssueを見てよくよく考えてみたら「これは不具合ではない」ということに気が付きました…。

記事の最初に書いてあるとおり「フォームをサブミットすると、フォーム内容がリセットされる」というのはReact関係なく普通のHTMLでは当たり前の挙動です。むしろ、Next.js v14の内部で使われていたReact Canary当時のバージョン動作の「フォーム内容が残る」状態のほうが特殊です。

ただ、その挙動がNext.js/Reactの新仕様だと勘違いし、依存したコードを書いてしまったために「不具合か?」という勘違いをしてしまいました。とはいえ、挙動が変わったので対応をしないといけません。

v15でフォームの内容を残す回避策

対応方法はいくつかあります。

useFormActionを使う

page.tsx
"use client";

import { submit } from "@/app/useActionState/action";
import { useActionState } from "react";

const initialState = { name: "" };
export default function Page() {
  const [state, action] = useActionState(submit, initialState);
  return (
    <form action={action}>
      <input type="text" name="name" defaultValue={state.name} />
      <button type="submit">送信</button>
    </form>
  );
}
  • useActionState を使って入力値をステート管理する
  • ServerActionの返り値で入力内容を返すことで初期値が維持される
  • 基本はこのやり方を使うのが良い

onSubmitを使う

page.tsx
"use client";

import { submit } from "@/app/onSubmit/action";
import type { FormEvent } from "react";

export default function Page() {
  const action = async (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    const formData = new FormData(event.currentTarget);
    await submit(formData);
  };
  return (
    <form onSubmit={action}>
      <input type="text" name="name" />
      <button type="submit">送信</button>
    </form>
  );
}
  • action ではなく onSubmit でクライアント側の制御にします
  • こうすることでフォームの再描画をさせずにサブミット処理ができます
  • このやり方を積極的に利用することはあまりない気がします
    • ほかのuseStateと連携する場合には使うかも

さいごに

今回の挙動変更は特に非制御コンポーネントでフォームを作成している時に大きく影響を受けると思います。またフォームライブラリを利用している場合にも影響を受けることもあるかもしれません。

ReactのIssueで現在も議論されています。将来的にオプションでリセットされないように拡張するのか、このままなのかは引き続きウォッチしていきたいと思います(拡張プルリクは出ているけど…)。

ムーザルちゃんねる

Discussion