useFormStatus

useFormStatus で親 form の Action 実行状態を送信ボタンへ伝える方法を学ぶ。

useFormStatus は、親の <form> が Action を実行している状態を読む React DOM Hook です。送信ボタンを二重送信から守り、送信中の文言を出す用途に向いています。

form の子で pending を読む

import { useFormStatus } from 'react-dom';

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

function ContactForm() {
  async function submit(formData: FormData) {
    await sendMessage(String(formData.get('message')));
  }
  return <form action={submit}><textarea name="message" /><SubmitButton /></form>;
}

送信ボタンを独立させることで、ボタン内部だけで pending に応じた文言・disabled・aria-busy を扱えます。フォーム本体は送信するデータと Action に集中できます。送信中に disabled にするのは UX だけでなく、同じ操作を誤って複数回送ることを避ける基本的な対策でもあります。

SubmitButton は form の内側に描画される子である必要があります。Hook を呼ぶコンポーネント自身が form を返す場合、その form の状態は読めません。

これは Hook が最も近い親 form の Context を読むためです。フォーム本体と送信ボタンを別コンポーネントにする構成は、ボタン以外の子コンポーネントにも pending 状態を渡せる利点があります。複数の form がある画面でも、それぞれの子は自分の親 form の状態だけを読みます。

useActionState との選び方

送信中かどうかだけを form の深い子で使うなら useFormStatus が簡潔です。Action の戻り値を表示したい、送信関数そのものを扱いたい場合は useActionState を選びます。両方を同じフォームで使うこともできます。

pending 以外にも data、method、action が返りますが、まずは二重送信を防ぎ、送信中を明示する基本の UI から始めるのがよいでしょう。エラー内容や送信成功後の画面遷移は、form status とは別に設計します。

フォームの外では使えない

useFormStatus は最寄りの親 form を探すため、form の外に置いたグローバルな送信中表示には使えません。そのような画面全体の状態が必要なら、Action を呼ぶ親で state を管理するか、フレームワークの navigation 状態を利用します。どの form の pending かが明確な範囲に閉じ込めるのが、この Hook の役割です。

まとめ

useFormStatus は form 境界の pending 状態を子へ伝えます。送信ボタンを小さなコンポーネントに分け、親 form の中で呼びましょう。

Sources