React 19のuseFormStatusでフォーム送信状態を管理する
Posted: 2026-02-08
useFormStatusとは
React 19では <form> 要素の action プロパティに非同期関数を渡せるようになりました。これにより、フォーム送信をReactが直接管理できるようになっています。
useFormStatus は react-dom から提供されるHookで、この <form action> と組み合わせて使用します。フォーム送信中かどうか、送信されたデータは何か、といったステータス情報を子コンポーネントからリアルタイムに取得できます。
従来のフォーム送信状態管理では、useState で isSubmitting のようなフラグを用意し、onSubmit ハンドラの前後で手動的にtrue/falseを切り替える必要がありました。
// 従来の方法
function ContactForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
async function handleSubmit(e) {
e.preventDefault();
setIsSubmitting(true);
try {
await fetch("/api/contact", { method: "POST", body: new FormData(e.target) });
} finally {
setIsSubmitting(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" />
<button disabled={isSubmitting}>
{isSubmitting ? "送信中..." : "送信"}
</button>
</form>
);
}
useFormStatus を使えば、この状態管理を宣言的かつ簡潔に書けるようになります。
APIリファレンス
インポート
import { useFormStatus } from "react-dom";
使い方
const { pending, data, method, action } = useFormStatus();
返り値
useFormStatus は以下のプロパティを持つオブジェクトを返します。
pending(boolean) — 親の<form>が送信中であればtrue、それ以外はfalsedata(FormData | null) — 送信中のフォームデータ。送信中でなければnullmethod(string) — HTTPメソッド('get'または'post')。デフォルトは'get'action(function | null) — 親の<form>のactionプロパティに渡された関数への参照。actionが未指定またはURL文字列の場合はnull
重要な制約
useFormStatus は <form> の子コンポーネント内でのみ使用可能 です。同じコンポーネント内でレンダリングされた <form> のステータスは取得できません。この制約については後述の「よくある間違い」セクションで詳しく説明します。
基本的な使用例: 送信ボタンの無効化とローディング表示
最も一般的なユースケースは、フォーム送信中にボタンを無効化してローディング表示を出すことです。
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "送信中..." : "送信"}
</button>
);
}
function ContactForm() {
async function handleSubmit(formData) {
await fetch("/api/contact", {
method: "POST",
body: formData,
});
}
return (
<form action={handleSubmit}>
<input type="text" name="name" placeholder="お名前" required />
<input type="email" name="email" placeholder="メールアドレス" required />
<textarea name="message" placeholder="メッセージ" required />
<SubmitButton />
</form>
);
}
ポイントは、useFormStatus を呼び出す SubmitButton が <form> の子コンポーネントとして配置されていることです。ContactForm 内で直接 useFormStatus を呼んでも、そのコンポーネント自身がレンダリングする <form> のステータスは取得できません。
送信中のフォームデータを読み取る
data プロパティを使うと、送信中のフォームデータにアクセスできます。これにより、ユーザーが入力した内容を送信中に表示する「楽観的UI(Optimistic UI)」のようなパターンを実装できます。
function SubmittingMessage() {
const { pending, data } = useFormStatus();
if (!pending) return null;
return (
<p>
「{data?.get("name")}」さんのメッセージを送信しています...
</p>
);
}
function ContactForm() {
async function handleSubmit(formData) {
await fetch("/api/contact", {
method: "POST",
body: formData,
});
}
return (
<form action={handleSubmit}>
<input type="text" name="name" placeholder="お名前" required />
<input type="email" name="email" placeholder="メールアドレス" required />
<textarea name="message" placeholder="メッセージ" required />
<SubmitButton />
<SubmittingMessage />
</form>
);
}
送信中は data に FormData オブジェクトが入るので、data.get("name") のように各フィールドの値を取得できます。送信が完了すると pending が false に戻り、data は null になります。
よくある間違い: 同じコンポーネント内での使用
useFormStatus を使う上で最もよくある間違いは、<form> をレンダリングするコンポーネントと同じコンポーネント内で呼び出してしまうことです。
// NG: 同じコンポーネント内で使用
function Form() {
const { pending } = useFormStatus(); // 常に pending: false になる
return (
<form action={submitAction}>
<button disabled={pending}>送信</button>
</form>
);
}
この場合、useFormStatus は常に pending: false を返します。useFormStatus が参照するのは親の <form> であり、同じコンポーネント内の <form> ではないためです。
正しくは、useFormStatus を使う部分を別のコンポーネントに切り出します。
// OK: 子コンポーネントに分離
function SubmitButton() {
const { pending } = useFormStatus(); // 正しく動作する
return (
<button type="submit" disabled={pending}>
{pending ? "送信中..." : "送信"}
</button>
);
}
function Form() {
return (
<form action={submitAction}>
<SubmitButton />
</form>
);
}
useActionStateとの組み合わせ
useFormStatus はフォーム送信中のUI状態(ローディング表示など)を管理するのに適していますが、サーバーからの応答結果(成功・失敗やエラーメッセージなど)を管理するには useActionState を併用するのが効果的です。
import { useActionState } from "react";
import { useFormStatus } from "react-dom";
function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? "送信中..." : "送信"}
</button>
);
}
function ContactForm() {
const [state, formAction] = useActionState(async (prevState, formData) => {
const res = await fetch("/api/contact", {
method: "POST",
body: formData,
});
if (!res.ok) {
return { success: false, message: "送信に失敗しました。" };
}
return { success: true, message: "送信が完了しました。" };
}, null);
return (
<form action={formAction}>
<input type="text" name="name" placeholder="お名前" required />
<input type="email" name="email" placeholder="メールアドレス" required />
<textarea name="message" placeholder="メッセージ" required />
<SubmitButton />
{state?.message && (
<p style={{ color: state.success ? "green" : "red" }}>
{state.message}
</p>
)}
</form>
);
}
このように2つのHookを組み合わせることで、役割を明確に分担できます。
useFormStatus— 送信中のUI状態(ローディング、ボタン無効化)を管理useActionState— サーバーからの応答結果(成功/失敗、エラーメッセージ)を管理
まとめ
useFormStatus は、React 19の <form action> と組み合わせて使うことで、フォーム送信中のUI状態を宣言的に管理できるHookです。
pendingでローディング表示やボタンの無効化を簡潔に実装できるdataで送信中のフォームデータにアクセスし、楽観的UIを実現できるuseActionStateと組み合わせることで、送信状態と応答結果の両方を効率的に管理できる<form>の子コンポーネント内で使う という制約に注意が必要
参考URL:
React公式ドキュメント: useFormStatusReact 19リリースブログ
