< Back

React 19のuseFormStatusでフォーム送信状態を管理する

Posted: 2026-02-08


useFormStatusとは

React 19では <form> 要素の action プロパティに非同期関数を渡せるようになりました。これにより、フォーム送信をReactが直接管理できるようになっています。

useFormStatusreact-dom から提供されるHookで、この <form action> と組み合わせて使用します。フォーム送信中かどうか、送信されたデータは何か、といったステータス情報を子コンポーネントからリアルタイムに取得できます。


従来のフォーム送信状態管理では、useStateisSubmitting のようなフラグを用意し、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 は以下のプロパティを持つオブジェクトを返します。


重要な制約

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>
  );
}

送信中は dataFormData オブジェクトが入るので、data.get("name") のように各フィールドの値を取得できます。送信が完了すると pendingfalse に戻り、datanull になります。



よくある間違い: 同じコンポーネント内での使用

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 は、React 19の <form action> と組み合わせて使うことで、フォーム送信中のUI状態を宣言的に管理できるHookです。





参考URL:

React公式ドキュメント: useFormStatus
React 19リリースブログ



Share this post