useFormState - This feature is available in the latest Canary

Canary

useFormState フックは、現在 React の Canary および experimental チャンネルでのみ利用可能です。リリースチャンネルについてはこちらをご覧ください。また、useFormState の利点をフルに活かすためには、React Server Components をサポートするフレームワークを使用する必要があります。

useFormState は、フォームアクションの結果に基づいて state を更新するためのフックです。

const [state, formAction] = useFormState(fn, initialState, permalink?);

リファレンス

useFormState(action, initialState, permalink?)

コンポーネントのトップレベルで useFormState を呼び出してコンポーネントの state を作成し、フォームアクションが呼び出されたときに更新されるようにします。既存のフォームアクション関数と初期 state を useFormState に渡し、フォームで使用する新しいアクションと最新のフォーム state が返されます。あなたが渡した関数にも、最新のフォーム state が渡されるようになります。

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

フォーム state とは、フォームが最後に送信されたときにアクションによって返される値です。フォームがまだ送信されていない場合は、渡された初期 state が使われます。

サーバアクションと併用して useFormState を使うことで、ハイドレーションが完了する前にフォームが送信された場合でも、そのサーバからのレスポンスを表示できるようになります。

さらに例を見る

引数

  • fn: フォームが送信されたりボタンが押されたりしたときに呼び出される関数。この関数が呼び出される際には、1 番目の引数としてはフォームの前回 state(初回は渡した initialState、2 回目以降は前回の返り値)を受け取り、次の引数としてはフォームアクションが通常受け取る引数を受け取ります。
  • initialState: state の初期値として使いたい値。シリアライズ可能な任意の値です。この引数はアクションが一度呼び出された後は無視されます。
  • 省略可能 permalink: このフォームが書き換えの対象とするユニークなページ URL を含んだ文字列。ダイナミックなコンテンツ(ページフィードなど)のあるページでプログレッシブエンハンスメントを組み合わせる場合に使用します。fnサーバアクションであり、かつフォームが JavaScript バンドルの読み込み完了前に送信された場合、ブラウザは現在のページ URL ではなくこの指定されたパーマリンク用 URL に移動するようになります。React が state を正しく受け渡せるよう、移動先となるページでも(アクション fnpermalink も含む)同じフォームが必ずレンダーされるようにしてください。フォームのハイドレーションが完了した後は、このパラメータは無視されます。

返り値

useFormState は 2 つの値を含む配列を返します。

  1. 現在の state。初回レンダー時には、渡した initialState と等しくなります。アクションが呼び出された後は、そのアクションが返した値と等しくなります。
  2. フォームコンポーネントの action プロパティや、フォーム内の任意の button コンポーネントの formAction プロパティとして渡すことができる新しいアクション。

注意点

  • React Server Components をサポートするフレームワークで使用する場合、useFormState はクライアント上で JavaScript が実行される前にフォームを操作可能にできます。Server Components なしで使用する場合、コンポーネントのローカル state と同様のものになります。
  • useFormState に渡される関数は、追加の 1 番目の引数として、前回 state ないし初期 state を受け取ります。従って useFormState を使用せずに直接フォームアクションとして使用する場合とは異なるシグネチャになります。

使用法

フォームアクションによって返された情報の使用

コンポーネントのトップレベルで useFormState を呼び出し、最後にフォームが送信された際のアクションの返り値にアクセスします。

import { useFormState } from 'react-dom';
import { action } from './actions.js';

function MyComponent() {
const [state, formAction] = useFormState(action, null);
// ...
return (
<form action={formAction}>
{/* ... */}
</form>
);
}

useFormState は、2 つの項目を含む配列を返します。

  1. フォームの state の現在値。初期値はあなたが渡した 初期 state となり、フォームが送信された後はあなたが渡したアクションの返り値となります。
  2. <form> の props である action に渡せる新しいアクション

フォームが送信されると、あなたが渡したアクション関数が呼び出されます。その返り値が、新たなフォームの state 現在値になります。

あなたが渡すアクションは、新たな第 1 引数として、フォームのstate の現在値を受け取ります。フォームが初めて送信されるとき、これはあなたが渡した初期 state と等しくなります。次回以降の送信では、アクションが前回呼び出されたときの返り値になります。残りの引数は useFormState を使用しなかった場合と同じです。

function action(currentState, formData) {
// ...
return 'next state';
}

フォーム送信後に情報を表示

1/2:
フォームエラーの表示

サーバアクションによって返されるメッセージをエラーメッセージやトーストとして表示するには、そのアクションを useFormState の呼び出しでラップします。

import { useState } from "react";
import { useFormState } from "react-dom";
import { addToCart } from "./actions.js";

function AddToCartForm({itemID, itemTitle}) {
  const [message, formAction] = useFormState(addToCart, null);
  return (
    <form action={formAction}>
      <h2>{itemTitle}</h2>
      <input type="hidden" name="itemID" value={itemID} />
      <button type="submit">Add to Cart</button>
      {message}
    </form>
  );
}

export default function App() {
  return (
    <>
      <AddToCartForm itemID="1" itemTitle="JavaScript: The Definitive Guide" />
      <AddToCartForm itemID="2" itemTitle="JavaScript: The Good Parts" />
    </>
  )
}

トラブルシューティング

アクションが送信されたフォームデータを読み取れなくなった

useFormState でアクションをラップすると、追加の引数が 1 番目の引数として加わります。したがって、通常は 1 番目の引数であるはずの送信フォームデータは、2 番目の引数になります。追加される新しい第 1 引数は、フォーム state の現在値です。

function action(currentState, formData) {
// ...
}