Menu

React useActionStateの使い方:フォーム、エラー、保留中

useActionStateは非同期のアクションを包み、最後に返された値を保ち、まだ実行中かどうかを教えてくれます。シグネチャ、フォームアクション、バリデーションエラーの返し方、フォームの外での呼び方を学びます。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

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 に渡してください。

Coddyのプログラミング言語のイラスト

Coddyでコードを学ぼう

始める