📚

Next.js 14→15→16: useFormState / useActionState / useFormStatus を“迷わず”

に公開

Next.js 14→15→16: useFormState / useActionState / useFormStatus を“迷わず”使い分ける

この記事は、Next.js 14 / 15 / 16 の **フォーム送信(Server Actions / Server Functions)**で混乱しがちな
useFormState / useActionState / useFormStatus を、初心者が一発で整理できるようにまとめたものです。
(結論→理由→コピペできるコード例→移行手順の順に書きます)


結論(迷ったらここだけ見ればOK)

Next.js 14(React 18 系の時代のドキュメント)

  • 状態を“サーバー処理の戻り値”で更新したいuseFormStatereact-dom から import)
  • 送信中だけボタンを disable したいuseFormStatusreact-dom から import)
  • この時代の useFormStatus()pending だけを返す説明になっています(他のキーは出てきません)。

Next.js 15 以降(React 19 系へ)

  • useFormStateuseActionState に置き換え(Next.js 公式 Upgrade Guide に明記)
  • useFormStatus()pending / data / method / action を返す(Next.js 公式 Upgrade Guide に明記)

Next.js 16

  • 公式 Upgrade Guide(version-16)に この3フックの追加変更は記載されていません
    したがって React 19 の公式 API(useActionState / useFormStatus)に従って書くのが正解です。

まず“名前が変わっただけ?”をはっきりさせる(ここが混乱の原因)

1) useFormStateuseActionState は、React の正式 API 名に合わせた置き換え

React 公式ドキュメントで、useActionState

  • const [state, formAction, isPending] = useActionState(fn, initialState, permalink?)
  • 以前の React Canary では、この API が React DOM にあり useFormState と呼ばれていた

と明記されています。

つまり、Next.js 15 で突然“別物になった”のではなく
React 側で正式名称が useActionState に定まったので、Next.js 側もそれに合わせた、という整理です。


それぞれ“何をするフックなのか”(用途を 1 行で)

  • useActionState(旧 useFormState
    サーバー処理(Action)の戻り値を、クライアント側の state として保持する(送信中フラグも取れる)

  • useFormStatus
    直近のフォーム送信の状態(送信中か/送っている FormData/method/action)を取る
    ※ただし「フォームの内側で描画される子コンポーネント」で呼ぶ必要がある


コピペで動く:Next.js 15/16(useActionState + useFormStatus)

ここでは「Server Action(Server Function)」の結果を画面に出し、送信中はボタンを disable にします。
初心者がつまずきやすい “どこに何を書くか” を、最小構成でまとめています。

1) サーバー側(action.ts)

'use server'

export async function createTodo(prevState: { message: string }, formData: FormData) {
  const title = String(formData.get('title') ?? '')

  if (!title.trim()) {
    return { message: 'タイトルは必須です' }
  }

  // ここで DB 保存などをする想定
  return { message: `作成しました: ${title}` }
}

ポイント:

  • useActionState に渡す action は (prevState, formData) の形にする(React 公式の例もこの形)
  • 戻り値が、そのままクライアント側の state になる

2) クライアント側(form.tsx)

'use client'

import { useActionState } from 'react'
import { useFormStatus } from 'react-dom'
import { createTodo } from './action'

function SubmitButton() {
  const { pending } = useFormStatus()
  return (
    <button type="submit" disabled={pending}>
      {pending ? '送信中…' : '送信'}
    </button>
  )
}

export default function TodoForm() {
  const initialState = { message: '' }
  const [state, formAction, isPending] = useActionState(createTodo, initialState)

  return (
    <form action={formAction}>
      <input name="title" placeholder="タイトル" />
      <SubmitButton />
      <p aria-live="polite">{state.message}</p>

      {/* これはデバッグ用(useFormStatus と同じ pending だけど、hook 側でも取れる) */}
      <small>isPending: {String(isPending)}</small>
    </form>
  )
}

ポイント:

  • useFormStatus()フォームの子コンポーネントで呼ぶSubmitButton を分けている理由)
  • useActionState[state, formAction, isPending] の 3 つが返る(React 公式)
  • <form action={formAction}>action に、useActionState が返した formAction を渡す

Next.js 14 で同じことをやる(useFormState + useFormStatus)

Next.js 14 の公式ドキュメント(Server Actions and Mutations)では、

  • useFormStatus は **“送信中かどうか”**として pending を使う例
  • useFormState(action, initialState) を渡して [state, formAction] を受け取る例

が提示されています。

Next.js 14 版の最小例(概念は同じ)

'use client'

import { useFormState, useFormStatus } from 'react-dom'
import { createTodo } from './action' // 'use server' 側

function SubmitButton() {
  const { pending } = useFormStatus()
  return <button type="submit" disabled={pending}>{pending ? '送信中…' : '送信'}</button>
}

export default function TodoForm() {
  const initialState = { message: '' }
  const [state, formAction] = useFormState(createTodo, initialState)

  return (
    <form action={formAction}>
      <input name="title" />
      <SubmitButton />
      <p aria-live="polite">{state.message}</p>
    </form>
  )
}

14 → 15(→16)移行の“手順”(やることはこれだけ)

手順 1:import とフック名を置き換える

  • react-domuseFormStatereactuseActionState

手順 2:戻り値の 3 番目(isPending)を使いたければ受け取る

  • Next.js 14 の useFormState 例は [state, formAction]
  • React 19 の useActionState[state, formAction, isPending]

手順 3:useFormStatus の戻り値を “過剰に destructuring しない”

  • Next.js 15 以降は pending / data / method / action がある
  • ただし “送信中ボタン制御” だけなら pending だけで十分

よくある質問

Q1. 「useFormState は useActionState に“名前が変わっただけ”で、使い方も引数も戻り値も全く同じ」?

違います。
React 公式では useActionState3 つ目に isPending を返す形で説明されています。
また、React 公式で「以前は useFormState と呼ばれていた」と明記されているので、
“同一 API のリネーム” と整理してよいですが、戻り値まで“全く同じ”と言い切るのは誤りです。

Q2. useFormStatus は Next.js 15/16 でも同じ?

**名前は同じ(useFormStatus のまま)です。
ただし、Next.js 15 の公式 Upgrade Guide では、useFormStatus追加で data / method / action を含むと明記されています。
つまり、
「同じ名前だが、返せる情報が増えた」**が正確です。


公式ドキュメント(参照先)

  • Next.js 14: Server Actions and Mutations(useFormStatus / useFormState の例あり)
  • Next.js 15: Upgrade Guide(useFormState → useActionState、useFormStatus の追加プロパティ)
  • React 公式: useActionState / useFormStatus の API リファレンス

Discussion