React19から使えるuseActionStateはuseReducerの非同期版
この記事のまとめ
useActionStateは<form>
と共に利用する例が紹介されています。
もちろんその用途でも使えますが、useActionStateは非同期関数を扱えるuseReducerに似たフックで汎用hooksです。
対象読者
- react19のuseActionStateについて知りたい
- ある程度reactを知っている
- Next.jsをある程度知っている
useActionStateとは
公式では<form>
とともに使う例が示されており、submitボタンを押したときの処理を記述するhooksのように見えます。
import { useActionState } from "react";
async function increment(previousState, formData) {
return previousState + 1;
}
function StatefulForm({}) {
const [state, formAction] = useActionState(increment, 0);
return (
<form>
{state}
<button formAction={formAction}>Increment</button>
</form>
)
}
しかし、useActionStateの引数や戻り値の型をよく見てください。見覚えありませんか?
import { useReducer } from 'react';
function reducer(state, action) {
// ...
}
function MyComponent() {
const [state, dispatch] = useReducer(reducer, { age: 42 });
// ...
useReducerはreducerという関数と初期値を受け取ります。
reducerは現在の状態とpayloadを受け取り、新しい状態を返します。
useActionStateと似ています。どちらも現在の状態とpayloadを受け取り新しい状態を返す関数と、初期値を引数に受け取ります。
useReducerとuseActionStateの違い
大きな違いが1つあります。
useActionStateは非同期関数を使えます。そして、非同期関数が実行中か判定する変数を返却します。
const action = async (oldState: State, payload: unknown): State => {
// oldStateとpayloadから新しい状態を計算
// async関数なのでfetchなどをawaitできる
return newState;
}
const [state, dispatch, isPending] = useActionState(action, initialState);
非同期関数を使えるということで、Next.jsで利用できるserver actionsも使えます。
actionは非同期関数を登録できますが、dispatchは同期関数です。
どういう挙動になるか見てみました。
dispatchが同期関数なのにactionが非同期関数で動くわけ
dispatchはactionをスケジューリングする関数であり、dispatch関数内でactionを実行しているわけではありません。ここがuseReducerと異なる点です。
useReducerはdispatch関数の内部でactionを実行しているので、actoinに重たい処理を書くことはご法度でした。
useActionStateのdispatchはスケジューリングだけなので、呼び出してもユーザーの操作をブロックしません。
useActionStateは裏側でuseTransitionを使っています。
なので、制御を入れないと前のaction実行中にdispatchを何度も呼ぶことができます。
dispatchを何度も読んだらどうなるのでしょうか
actionが直列にスケジューリングされます。
ちゃんと順番通りに実行してくれるので、結果が壊れることはありません。
ただし、非同期関数が何個もスケジューリングさせる必要ないと思うので、isPendingでボタンを押せなくする等の制御した方が良いです。
formのactionになぜ使っているのか
formのactionは別に前の状態を参照する必要ないですし、新しい状態を返す必要もないです。
import { useActionState } from 'react';
import { action } from './actions.js';
function MyComponent() {
const [state, formAction, isPending] = useActionState(action, null);
// ...
return (
<form action={formAction}>
{/* ... */}
</form>
);
}
formの内容を登録するserver actionをactionとして、初期値・戻り値ともにnullにすることで、formのactionを管理するhooksとしても使えます。この時のpayloadはformData
です。
action実行中にユーザーの操作をブロックせず、actionが実行中かどうか簡単に管理できます。
この使い方もとても便利ですが、useActionStateはform専用ではなく、もっと汎用なhooksです。
最後に
react 19の正式リリースされていないので変わる可能性があります。
また、私の理解が異なっていたらコメント等にて教えてください。
Discussion