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 系の時代のドキュメント)
-
状態を“サーバー処理の戻り値”で更新したい →
useFormState(react-domから import) -
送信中だけボタンを disable したい →
useFormStatus(react-domから import) - この時代の
useFormStatus()はpendingだけを返す説明になっています(他のキーは出てきません)。
Next.js 15 以降(React 19 系へ)
-
useFormStateはuseActionStateに置き換え(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) useFormState → useActionState は、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-domのuseFormState→reactのuseActionState
手順 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 公式では useActionState は 3 つ目に 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