useFormState から useActionState に移行する
はじめに
useActionState とは useFormState に取って代わる新しい React Hooks です。
既存の useFormState の問題点を解消するために導入されました。
useFormState
useFormState とは渡されたアクションの結果に基づき state を更新するためのフックです。
const [state, formAction] = useFormState(fn, initialState, permalink?);
このように useFormState にアクションと初期 state を渡すことにより、フォームが送信された後のアクションの返り値と <form> や <button> に渡せる新しいアクションを取得できます。useFormState は <form> 内でなくても使用できます。
import { useFormState } from "react-dom";
async function increment(previousState, formData) {
return previousState + 1;
}
function StatefulForm({}) {
const [state, formAction] = useFormState(increment, 0);
return (
<form>
{state}
<button formAction={formAction}>Increment</button>
</form>
)
}
useFormStatus
一方 useFormStatus というフックはフォーム送信時のステータス情報を提供します。
const { pending, data, method, action } = useFormStatus();
ステータス情報を取得するには useFormStatus を使って構成してるコンポーネントが <form> 内でレンダーされる必要があります。status オブジェクトにはこちらのプロパティが含まれます。
-
pending:フォームが送信中かどうかを示す -
data:送信中のデータ -
method:GETとPOSTのどちらで送信されるかによってgetまたはpostの文字列 -
action:<form>のactionに渡された関数
import { useFormStatus } from "react-dom";
import action from './actions';
function Submit() {
const status = useFormStatus();
return <button disabled={status.pending}>Submit</button>
}
export default function App() {
return (
<form action={action}>
<Submit />
</form>
);
}
useFormState の問題点
しかし useFormState にはこれらの問題点がありました。
-
useFormStatusにはpendingがありますが、useFormStateにはありません。そしてuseFormStateにpendingプロパティを追加すると、混乱が生じ得ました。なぜなら、useFormStateは渡されたアクションのpending状態を返し、アクションが適用される<form>のpending状態は返さない為です。useFormStateはuseFormStatusと異なり、<form>内で使用される必要はなく、名前から想起されるような直接的なフォームの状態管理よりも、アクションに基づいた状態管理を行います。 -
さらに
useFormStateによって返される新しいアクションが複数のフォームに適用されると、useFormStateは複数のフォーム送信により矛盾したpending状態を生じさせるでしょう。この問題は、useActionStateの導入により解決されます。useActionStateはアクション毎に独立したpending状態を提供することにより、アクションがどのフォームに適用されるかに関わらず、そのアクションの状態を正確に追跡できるようにします。 -
useFormStateはReactDOMパッケージからのみエクスポートされ使用が限定されてました。しかし、アクションは特定の<form>に関連しないため、本来useFormStateはReactDOMのみならずReact Native(<form>なし)など任意のレンダラーで使用できます。
useActionState の導入
これらの問題点に対処するため useFormState は useActionState に改名されました。
主な変更点はこちらです。
- 返されるタプルに
pendingが追加されます。これによって、アクションの非同期処理が完了するまでの間、そのアクションがpending状態かどうかを正確に追跡できます。この変更は複数のフォームやコンポーネントに同じアクションを使用してても、どのアクションが処理中であるかをUIに明確に示すことを可能にします。 -
ReactDOMパッケージからreactパッケージに移動されました。
これによりReact Nativeなどの任意のレンダラーで使用できるようになります。
useActionState は pending のほかこちらのタプルを返します。
-
state:アクションの最後の返り値 -
dispatch:新しいアクションをディスパッチするためのメソッド
ディスパッチされたアクションの state の更新は、アクションが完了し、UIが更新されるまで、ページを応答性がある状態に保つためにトランジションでラップされます。その為、アクションの処理中でもユーザーはページの他の部分をスムーズに操作できます。
function useActionState<State>(
action: (state: Awaited<State>) => State | Promise<State>,
initialState: Awaited<State>,
permalink?: string,
): [state: Awaited<State>, dispatch: () => void, boolean];
useActionState は useFormState と同じように使用できます。
import { useActionState } from "react";
function Form({ formAction }) {
const [state, action, isPending] = useActionState(formAction);
return (
<form action={action}>
<input type="email" name="email" disabled={isPending} />
<button type="submit" disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</form>
);
}
また useFormState と同じように <form> と使用する必要はありません。
import { useActionState, useRef } from "react";
function Form({ someAction }) {
const ref = useRef(null);
const [state, action, isPending] = useActionState(someAction);
async function handleSubmit() {
// See caveats below
await action({ email: ref.current.value });
}
return (
<div>
<input ref={ref} type="email" name="email" disabled={isPending} />
<button onClick={handleSubmit} disabled={isPending}>
Submit
</button>
{state.errorMessage && <p>{state.errorMessage}</p>}
</div>
);
}
参照
自らの備忘録のために投稿してますが、なにかお役に立てましたら幸いです!👏
また、なにか間違ってましたらご指摘いただけますと幸いです!🙏
Discussion
わかりやすい記事でした!
react-hook-form + yupでフォーム実装してるプロダクトが多いと思います。
今回の記事で
「useActionStateがreact-hook-formの代替になるか?そしたらyupはなくなるのか?(or 併用するのか?できるのか?)」
を知りたくなりました!
ありがとうございます!励みになります!
その辺りはこちらの記事がとても参考になると思います!🙏