useActionState

useActionState で非同期 Action の結果と pending 状態をまとめて扱う方法を学ぶ。

useActionState は Action の結果を state として受け取り、処理中かどうかも返す Hook です。フォーム送信などの非同期操作で、結果・エラー・pending 表示を近くにまとめられます。

Action の戻り値を次の state にする

import { useActionState } from 'react';

async function saveName(previous: string | null, formData: FormData) {
  const name = String(formData.get('name') ?? '');
  if (name.length === 0) return '名前を入力してください';
  await updateProfile({ name });
  return '保存しました';
}

function ProfileForm() {
  const [message, submitAction, isPending] = useActionState(saveName, null);
  return <form action={submitAction}><input name="name" /> <button disabled={isPending}>保存</button><p>{message}</p></form>;
}

最初の message は null、送信中は isPending が true、Action が返るとその戻り値が message になります。useState と useTransition を手で組み合わせる場合と比べ、Action に属する状態遷移が Hook の返り値にまとまります。

Action は直前の state と通常の引数を受け取り、返した値が次の state になります。フォームの action 属性に返却された関数を渡すと、FormData が引数として渡されます。

previous を使うと、送信後の成功メッセージ、入力検証エラー、サーバーから返ったデータを1つの state として更新できます。通常のイベントハンドラで setMessage と setPending を個別に同期するより、Action の開始から完了までが読みやすくなります。

使う判断と注意点

送信結果を画面に残すフォームや、非同期操作の pending 状態に向いています。一般的な state 更新やデータ取得の代わりではありません。Action は連続して呼ばれた場合に順番に処理されるため、独立した並列要求が必要なケースでは別の設計を検討します。

Action 内で例外を投げると、通常は Error Boundary が処理します。利用者に期待できる入力エラーは throw ではなく state として返す、予期しない失敗は Error Boundary や画面全体のエラー設計に委ねる、と責務を分けると分かりやすくなります。

progressive enhancement を意識する場合

フォーム action は、対応するフレームワークでは JavaScript の読み込み前から送信できる構成にもできます。その場合はサーバー側の検証が必須です。クライアントの validation は入力体験を良くしますが、信頼できる境界ではありません。Action がどこで実行されるかを確認して、認可と入力検証を適切な層へ置きます。

まとめ

useActionState は Action の結果と pending 状態を同じ境界で扱います。フォームの成功・失敗メッセージを手作業で同期する量を減らせます。

Sources