🦁

useActionStateをForm以外で使用する

2024/12/25に公開

はじめに

React公式ドキュメントに記載されているuseActionStateの使用例は、<form /> で使用する例しか書いていない。
ただ、別にフォームじゃなくても使えるはずなので、それを調査した。

この記事では触れないこと

  • useActionStateとは?

前提

ボタンをクリックして、useActionStateのformActionを呼び出すコンポーネントを作成して調査。

環境

    "next": "15.0.4",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",

調査結果1

ポイント

  • useActionStateに指定した関数fnの第1引数には現在のstateが、第2引数にはformActionに渡した値が入るため、第2引数をサーバーに送るなり、取り回せばOK

注意

ボタンをクリックすると、一応動くが、エラーメッセージが出る。
(actionをtransition外で実行しようとしているため)

An async function was passed to useActionState, but it was dispatched outside of an action context. This is likely not what you intended. Either pass the dispatch function to an `action` prop, or dispatch manually inside `startTransition`
'use client';

import { useActionState } from 'react';

export default function Page() {
  // クリックしたボタンの文字を2秒後に設定する

  const [str, setStr, isPending] = useActionState(
    // setStrに渡した値は、第2引数のnewValueに渡されるので、これを取り回せばOK
    async (current: string, newValue: string) => {
      // 2秒待つ
      await new Promise((resolve) => setTimeout(resolve, 2000));
      return newValue;
    },
    'initial value',
  );

  return (
    <div>
      <div>設定中の文字: {str}</div>
      <div className="flex gap-2">
        <button
          type="button"
          onClick={() => {
            setStr('hoge');
          }}
          disabled={isPending}
        >
          hoge
        </button>

        <button
          type="button"
          onClick={() => {
            setStr('fuga');
          }}
          disabled={isPending}
        >
          fuga
        </button>
      </div>
    </div>
  );
}

調査結果2(エラーを出ないようにした)

ポイント

  • useActionStateのformActionを、startTransitionで囲う
'use client';

import { startTransition, useActionState } from 'react';

export default function Page() {
  // クリックしたボタンの文字を2秒後に設定する

  const [str, setStr, isPending] = useActionState(
    // setStrに渡した値は、第2引数のnewValueに渡されるので、これを取り回せばOK
    async (current: string, newValue: string) => {
      // 2秒待つ
      await new Promise((resolve) => setTimeout(resolve, 2000));
      return newValue;
    },
    'initial value',
  );

  return (
    <div>
      <div>設定中の文字: {str}</div>
      <div className="flex gap-2">
        <button
          type="button"
          onClick={() => {
            startTransition(() => setStr('hoge'));
          }}
          disabled={isPending}
        >
          hoge
        </button>

        <button
          type="button"
          onClick={() => {
            startTransition(() => setStr('fuga'));
          }}
          disabled={isPending}
        >
          fuga
        </button>
      </div>
    </div>
  );
}

まとめ

  • useActionStateから返されるformActionを実行することで、actionを実行できる。
  • formActionに渡した値は、actionの第2引数に渡される
  • <form /> を使わない場合、startTransitionの内部で呼び出さないとエラーが起きる。
    • 上記の事例から、useActionStateは<form />以外での使用をあまり想定していないように感じるため、その場合はuseTransitionを使用するほうがベターかもしれない。
ASTRSK

Discussion