useActionState は、非同期の関数(アクション)を実行し、それが返したものをstateとして保存し、まだ実行中かどうかを教えてくれるReact 19のフックです。const [state, formAction, isPending] = useActionState(action, initialState) と呼び、formAction を <form> に渡します。
まず空のままフォームを送信してください。アクションがエラーのオブジェクトを返し、メッセージが表示されます。次に @ を含むアドレスを入力してもう一度送信します。1秒間、ボタンは「Subscribing...」と表示され、無効になります。
構文
const [state, formAction, isPending] = useActionState(action, initialState, permalink?);
actionは実行する関数です。Reactはそれをaction(previousState, ...args)として呼びます。それが返したもの(asyncなら解決した値)が新しいstateになります。initialStateは、アクションが一度も実行されていないときのstateです。最初のレンダリングでもコンポーネントがstate.messageを読めるように、アクションが返すのと同じ形にしてください。stateは最新の結果です。formActionはアクションを包んだ版です。<form action>や<button formAction>に渡すか、自分で呼びます。isPendingは、アクションが始まった瞬間から終わるまでtrueです。permalinkは省略可能で、フレームワークのServer Functionsでだけ意味があります。JavaScriptが読み込まれる前にフォームが送信されたときに、フォームが向かうべきURLです。
アクションは前回のstateを1つ目の引数として受け取ります。フォームを移行するときに一番よくある間違いがこれです。async function save(formData) と書くと formData に古いstateが入り、formData.get が例外を投げます。
FormDataでフォームを読む
formAction をフォームの action として使うと、Reactはフォームの FormData を渡してアクションを呼びます。name を持つすべての項目がそこに入っています。入力欄のための useState も onChange ハンドラも必要なく、e.preventDefault() も呼びません。Reactが送信イベントを処理してくれます。
Spicyのチェックを外してもう一度送信すると、記録されるデータから spicy のキーが消えます。チェックされていないチェックボックスは何も送らないからです。アクションは1を足すために previousState.count も読んでいます。前回のstateが最初に来るのはそのためです。
バリデーションエラーを返す
バリデーションはアクションの中に書きます。エラーをstateの一部として返し、ユーザーが入力した値も返して、フォームが元に戻せるようにします。フォームアクションが終わるとReactがフォームの非制御の項目をリセットするので、defaultValue={state.values...} のない項目は、送信に失敗した後に空に戻ってしまいます。
1文字の名前と短いパスワードを入力して送信してください。両方のエラーが表示され、両方の項目は入力したものを保っています。defaultValue={state.values.name} を削除してもう一度送信すると、エラーの後に名前の欄が空になります。
実際のアプリでは、最終的な判断はサーバーに任せてください。クライアントでのチェックはすばやいフィードバックを与えますが、誰でもフォームを使わずにリクエストを送れます。
フォームの外でアクションを呼ぶ
formAction は関数なので、ボタンやエフェクトから呼ぶこともできます。自分で呼ぶときは、呼び出しを startTransition で包んでください。フォームはこれを代わりにやってくれますが、普通の onClick はやってくれず、トランジションがないと isPending が正しく更新されません(開発中はReactがそれについてエラーを記録します)。
Load moreを何回かクリックしてください。各呼び出しは前回が返したstateから始まるので、ページは順番に追加されます。実行中にもう一度アクションを呼ぶと、Reactはその呼び出しを順番待ちに入れ、現在のものが終わってから、それぞれ最新のstateで実行します。
useActionStateの由来
React 19のリリース前、このフックはカナリア版で react-dom からインポートする useFormState として提供されていました。それが useActionState に名前を変え、react に移り、isPending の値を得ました。このような古いコードを見つけたら、直すのはインポートと名前です。
// React canary, before 19
import { useFormState } from 'react-dom';
const [state, formAction] = useFormState(action, initialState);
// React 19
import { useActionState } from 'react';
const [state, formAction, isPending] = useActionState(action, initialState);
深くネストした送信ボタンで保留中の状態を表示するには、react-dom の useFormStatus が、propsなしで親フォームの状態を読みます。React 19の概要で、ほかのフォームの機能と一緒に紹介しています。
エラーと、ほかのものを使うべきとき
アクションが例外を投げると、Reactはstateを更新する代わりに最も近いエラーバウンダリを表示します。バリデーションや使用済みのユーザー名のように、ユーザーが直せるエラーは、サインアップの例のようにstateとして返してください。本当の失敗は投げさせます。
useActionState はアクションの結果のためのものです。クリックしたら増えるべきいいねの数のように、アクションの実行中に即座に更新される値には、useOptimisticと組み合わせます。キーを押すたびに変わり、レンダリング中に読まれる入力には、普通の useState と制御された入力が今も正しい道具です。
よくある間違い
前回のstateの引数を忘れる。async function save(formData) は formData で前回のstateを受け取ってしまいます。フォームでは、シグネチャは常に (previousState, formData) です。
nameのない入力欄。FormData には name 属性を持つ項目しか含まれません。id しかない項目は formData.get(...) から抜け落ち、null が返ります。
どこでも制御された入力を使う。フォームアクションの中でも value と onChange は使えますが、使う必要はありません。name と defaultValue を持つ非制御の項目のほうがアクションには簡単に合い、送信後のReactのリセットが影響するのも非制御の項目だけです。
アクションの中でstateを読む。コンポーネントの state は、ハンドラを作ったレンダリング時点の値です。アクションの中では previousState を使ってください。複数の呼び出しが順番待ちになっていても、常に1つ前の呼び出しの結果です。
想定内のエラーを投げる。投げたエラーは、フォームを最も近いエラーバウンダリに置き換えてしまいます。パスワードの間違いはクラッシュではありません。stateとして返してください。
よくある質問
useActionStateは何を返しますか?
3つの要素の配列です。現在のstate(アクションが最後に返した値、まだ実行されていなければ初期state)、フォームに渡したり自分で呼んだりする新しいアクション、そしてアクションの実行中に true になる isPending です。
アクションはどんな引数を受け取りますか?
1つ目が前回のstateで、その次がアクションを呼んだときの引数です。<form action={formAction}> では、その2つ目の引数はフォームの FormData です。
useActionStateとuseFormStateの違いは何ですか?
同じフックです。Reactのカナリア版で react-dom の useFormState として提供され、React 19で useActionState に名前を変えて react に移り、同時に isPending の値が加わりました。
フォームなしでuseActionStateを使えますか?
はい。返されたアクションを、たとえばボタンの onClick から、startTransition の中で自分で呼びます。トランジションがないと isPending が正しく更新されず、開発中はReactがエラーを記録します。
送信後にフォームが空になるのはなぜですか?
フォームアクションが終わると、ブラウザが普通の送信の後にするのと同じように、Reactがフォームの非制御の項目をリセットするからです。エラーの後に値を残したいなら、それをstateで返し、項目の defaultValue に渡してください。