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 併用するのか?できるのか?)」
を知りたくなりました!
ありがとうございます!励みになります!
その辺りはこちらの記事がとても参考になると思います!🙏