🪝

useFormState から useActionState に移行する

2024/03/30に公開2

はじめに

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:送信中のデータ
  • methodGETPOST のどちらで送信されるかによって 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 にはありません。そして useFormStatepending プロパティを追加すると、混乱が生じ得ました。なぜなら、useFormState は渡されたアクションの pending 状態を返し、アクションが適用される <form>pending 状態は返さない為です。useFormStateuseFormStatus と異なり、<form> 内で使用される必要はなく、名前から想起されるような直接的なフォームの状態管理よりも、アクションに基づいた状態管理を行います。

  • さらに useFormState によって返される新しいアクションが複数のフォームに適用されると、useFormState は複数のフォーム送信により矛盾した pending 状態を生じさせるでしょう。この問題は、useActionState の導入により解決されます。useActionState はアクション毎に独立した pending 状態を提供することにより、アクションがどのフォームに適用されるかに関わらず、そのアクションの状態を正確に追跡できるようにします。

  • useFormStateReactDOM パッケージからのみエクスポートされ使用が限定されてました。しかし、アクションは特定の <form> に関連しないため、本来 useFormStateReactDOM のみならず React Native<form> なし)など任意のレンダラーで使用できます。

useActionState の導入

これらの問題点に対処するため useFormStateuseActionState に改名されました。
主な変更点はこちらです。

  • 返されるタプルに pending が追加されます。これによって、アクションの非同期処理が完了するまでの間、そのアクションが pending 状態かどうかを正確に追跡できます。この変更は複数のフォームやコンポーネントに同じアクションを使用してても、どのアクションが処理中であるかをUIに明確に示すことを可能にします。
  • ReactDOM パッケージから react パッケージに移動されました。
    これにより React Native などの任意のレンダラーで使用できるようになります。

useActionStatepending のほかこちらのタプルを返します。

  • 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];

useActionStateuseFormState と同じように使用できます。

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>
  );
}

参照

https://ja.react.dev/reference/react-dom/hooks/useFormState

https://ja.react.dev/reference/react-dom/hooks/useFormStatus

https://github.com/facebook/react/pull/28491

自らの備忘録のために投稿してますが、なにかお役に立てましたら幸いです!👏
また、なにか間違ってましたらご指摘いただけますと幸いです!🙏

Discussion

r-sugir-sugi

わかりやすい記事でした!

react-hook-form + yupでフォーム実装してるプロダクトが多いと思います。

今回の記事で
「useActionStateがreact-hook-formの代替になるか?そしたらyupはなくなるのか?(or 併用するのか?できるのか?)」
を知りたくなりました!