0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React Hooks を Hackしよう!【Part18: useActionStateをふかぼってみよう】

Last updated at Posted at 2025-12-19

React 19 で登場した useActionState は、フォームアクションの結果に基づいて state を更新するためのフックです。従来の useFormState(React DOM の一部)から名前が変更され、React 本体に統合されました。

💡 補足
React Canary の以前のバージョンでは、この API は React DOM の一部であり useFormState という名前でした。

1. なぜ useActionState が必要か

1.1 フォーム処理の課題

React でフォームを扱う場合、従来は以下のような課題がありました:

  • 状態管理の複雑さ: フォームの送信状態、エラー状態、成功状態を別々に管理する必要がある
  • 非同期処理の扱い: async/await を使った送信処理とUIの同期が煩雑
  • サーバーアクションとの連携: Server Components との統合が難しい
  • プログレッシブエンハンスメント: JavaScript が読み込まれる前のフォーム送信への対応
// ❌ 従来のフォーム処理(状態が分散)
function OldForm() {
  const [result, setResult] = useState(null);
  const [isPending, setIsPending] = useState(false);
  const [error, setError] = useState(null);
  
  const handleSubmit = async (e) => {
    e.preventDefault();
    setIsPending(true);
    setError(null);
    try {
      const formData = new FormData(e.target);
      const result = await submitForm(formData);
      setResult(result);
    } catch (err) {
      setError(err.message);
    } finally {
      setIsPending(false);
    }
  };
  
  return <form onSubmit={handleSubmit}>...</form>;
}

// ✅ useActionState を使用(シンプルで統一的)
function NewForm() {
  const [state, formAction, isPending] = useActionState(submitAction, null);
  
  return (
    <form action={formAction}>
      {state?.error && <p>{state.error}</p>}
      <button disabled={isPending}>送信</button>
    </form>
  );
}

1.2 useActionState の3つの特徴

特徴 説明
フォーム state の統一管理 アクションの結果に基づいて state を自動更新
pending 状態の自動追跡 isPending でアクションの進行状態を取得可能
SSR 対応 Server Components と連携し、JavaScript 読み込み前でも動作

1.3 useActionState の API

const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);
引数 説明
fn フォーム送信時に呼び出されるアクション関数。第1引数に前回の state、第2引数にフォームデータを受け取る
initialState state の初期値。シリアライズ可能な任意の値
permalink 省略可能。プログレッシブエンハンスメント用のURL
返り値 説明
state 現在の state。初回は initialState、以降はアクションの返り値
formAction <form>action プロパティに渡す新しいアクション
isPending アクションが処理中かどうかを表すフラグ

💡 ポイント
アクション関数のシグネチャは通常のフォームアクションと異なります。第1引数が「前回の state」になるため、フォームデータは第2引数で受け取ります。

2. useActionState の内部構造を徹底解剖

useActionState を使うたびに、React の内部では複数のモジュールが連携して動いています。この章では、facebook/react リポジトリの実際のコードを追いながら、その動作原理を解説します。

2.0 全体像: useActionState が動く仕組み

🎣 useActionState(フック呼び出し)
   ↓
📝 3つの Hook を内部で作成
   ├─ stateHook: アクション結果の state
   ├─ pendingStateHook: pending 状態
   └─ actionQueueHook: アクションキュー
   ↓
⚡ dispatchActionState でアクション実行
   ↓
🔄 順次実行キューで非同期処理を管理

重要なポイント:useActionState は内部で複数の Hook を組み合わせ、アクションを順次実行するキューを管理しています!

2.1 エントリポイント: packages/react/src/ReactHooks.js

// packages/react/src/ReactHooks.js

export function useActionState<S, P>(
  action: (Awaited<S>, P) => S,
  initialState: Awaited<S>,
  permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useActionState(action, initialState, permalink);
}

2.2 コア実装: mountActionState

初回レンダー時の処理

// packages/react-reconciler/src/ReactFiberHooks.js

function actionStateReducer<S>(oldState: S, newState: S): S {
  return newState;
}

function mountActionState<S, P>(
  action: (Awaited<S>, P) => S,
  initialStateProp: Awaited<S>,
  permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
  let initialState: Awaited<S> = initialStateProp;
  if (getIsHydrating()) {
    const root: FiberRoot = (getWorkInProgressRoot(): any);
    const ssrFormState = root.formState;
    // If a formState option was passed to the root, there are form state
    // markers that we need to hydrate. These indicate whether the form state
    // matches this hook instance.
    if (ssrFormState !== null) {
      const isMatching = tryToClaimNextHydratableFormMarkerInstance(
        currentlyRenderingFiber,
      );
      if (isMatching) {
        initialState = ssrFormState[0];
      }
    }
  }

  // State hook. The state is stored in a thenable which is then unwrapped by
  // the `use` algorithm during render.
  const stateHook = mountWorkInProgressHook();
  stateHook.memoizedState = stateHook.baseState = initialState;
  const stateQueue = {
    pending: null,
    lanes: NoLanes,
    dispatch: (null: any),
    lastRenderedReducer: actionStateReducer,
    lastRenderedState: initialState,
  };
  stateHook.queue = stateQueue;
  const setState: Dispatch<S | Awaited<S>> = (dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    ((stateQueue: any): UpdateQueue<S | Awaited<S>, S | Awaited<S>>),
  ): any);
  stateQueue.dispatch = setState;

  // Pending state. This is used to store the pending state of the action.
  // Tracked optimistically, like a transition pending state.
  const pendingStateHook = mountStateImpl((false: Thenable<boolean> | boolean));
  const setPendingState: boolean => void = (dispatchOptimisticSetState.bind(
    null,
    currentlyRenderingFiber,
    false,
    ((pendingStateHook.queue: any): UpdateQueue<
      S | Awaited<S>,
      S | Awaited<S>,
    >),
  ): any);

  // Action queue hook. This is used to queue pending actions. The queue is
  // shared between all instances of the hook. Similar to a regular state queue,
  // but different because the actions are run sequentially, and they run in
  // an event instead of during render.
  const actionQueueHook = mountWorkInProgressHook();
  const actionQueue: ActionStateQueue<S, P> = {
    state: initialState,
    dispatch: (null: any), // circular
    action,
    pending: null,
  };
  actionQueueHook.queue = actionQueue;
  const dispatch = (dispatchActionState: any).bind(
    null,
    currentlyRenderingFiber,
    actionQueue,
    setPendingState,
    setState,
  );
  actionQueue.dispatch = dispatch;

  // Stash the action function on the memoized state of the hook. We'll use this
  // to detect when the action function changes so we can update it in
  // an effect.
  actionQueueHook.memoizedState = action;

  return [initialState, dispatch, false];
}

💡 3つの内部 Hook
useActionState は内部で3つの Hook を作成します:

  1. stateHook: アクションの結果(state)を管理
  2. pendingStateHook: pending 状態を楽観的に更新
  3. actionQueueHook: アクションを順次実行するためのキュー

2.3 アクションキューの構造

// packages/react-reconciler/src/ReactFiberHooks.js

// useActionState actions run sequentially, because each action receives the
// previous state as an argument. We store pending actions on a queue.
type ActionStateQueue<S, P> = {
  // This is the most recent state returned from an action. It's updated as
  // soon as the action finishes running.
  state: Awaited<S>,
  // A stable dispatch method, passed to the user.
  dispatch: Dispatch<P>,
  // This is the most recent action function that was rendered. It's updated
  // during the commit phase.
  // If it's null, it means the action queue errored and subsequent actions
  // should not run.
  action: ((Awaited<S>, P) => S) | null,
  // This is a circular linked list of pending action payloads. It incudes the
  // action that is currently running.
  pending: ActionStateQueueNode<S, P> | null,
};

type ActionStateQueueNode<S, P> = {
  payload: P,
  // This is the action implementation at the time it was dispatched.
  action: (Awaited<S>, P) => S,
  // This is never null because it's part of a circular linked list.
  next: ActionStateQueueNode<S, P>,

  // Whether or not the action was dispatched as part of a transition.
  isTransition: boolean,

  // Implements the Thenable interface. We use it to suspend until the action
  // finishes.
  then: (listener: () => void) => void,
  status: 'pending' | 'rejected' | 'fulfilled',
  value: any,
  reason: any,
  listeners: Array<() => void>,
};

アクションキューの循環リスト構造

actionQueue.pending
       ↓
    Node1 → Node2 → Node3 → Node1(循環)
      ↑                        ↓
      └────────────────────────┘

2.4 アクションのディスパッチ: dispatchActionState

// packages/react-reconciler/src/ReactFiberHooks.js

function dispatchActionState<S, P>(
  fiber: Fiber,
  actionQueue: ActionStateQueue<S, P>,
  setPendingState: boolean => void,
  setState: Dispatch<ActionStateQueueNode<S, P>>,
  payload: P,
): void {
  if (isRenderPhaseUpdate(fiber)) {
    throw new Error('Cannot update form state while rendering.');
  }

  const currentAction = actionQueue.action;
  if (currentAction === null) {
    // An earlier action errored. Subsequent actions should not run.
    return;
  }

  const actionNode: ActionStateQueueNode<S, P> = {
    payload,
    action: currentAction,
    next: (null: any), // circular

    isTransition: true,

    status: 'pending',
    value: null,
    reason: null,
    listeners: [],
    then(listener) {
      actionNode.listeners.push(listener);
    },
  };

  // Check if we're inside a transition. If so, we'll need to restore the
  // transition context when the action is run.
  const prevTransition = ReactSharedInternals.T;
  if (prevTransition !== null) {
    // Optimistically update the pending state, similar to useTransition.
    setPendingState(true);
    setState(actionNode);
  } else {
    // This is not a transition.
    actionNode.isTransition = false;
    setState(actionNode);
  }

  const last = actionQueue.pending;
  if (last === null) {
    // There are no pending actions; this is the first one. We can run
    // it immediately.
    actionNode.next = actionQueue.pending = actionNode;
    runActionStateAction(actionQueue, actionNode);
  } else {
    // There's already an action running. Add to the queue.
    const first = last.next;
    actionNode.next = first;
    actionQueue.pending = last.next = actionNode;
  }
}

💡 レンダー中の更新は禁止
isRenderPhaseUpdate(fiber) でレンダー中かどうかをチェックし、レンダー中にアクションをディスパッチしようとするとエラーになります。これは useState と異なり、useActionState はレンダーフェーズの更新をサポートしていないためです。

2.5 アクションの実行: runActionStateAction

// packages/react-reconciler/src/ReactFiberHooks.js

function runActionStateAction<S, P>(
  actionQueue: ActionStateQueue<S, P>,
  node: ActionStateQueueNode<S, P>,
) {
  const action = node.action;
  const payload = node.payload;
  const prevState = actionQueue.state;

  if (node.isTransition) {
    // The original dispatch was part of a transition. We restore its
    // transition context here.
    const prevTransition = ReactSharedInternals.T;
    const currentTransition: Transition = ({}: any);
    ReactSharedInternals.T = currentTransition;
    try {
      const returnValue = action(prevState, payload);
      const onStartTransitionFinish = ReactSharedInternals.S;
      if (onStartTransitionFinish !== null) {
        onStartTransitionFinish(currentTransition, returnValue);
      }
      handleActionReturnValue(actionQueue, node, returnValue);
    } catch (error) {
      onActionError(actionQueue, node, error);
    } finally {
      ReactSharedInternals.T = prevTransition;
    }
  } else {
    // The original dispatch was not part of a transition.
    try {
      const returnValue = action(prevState, payload);
      handleActionReturnValue(actionQueue, node, returnValue);
    } catch (error) {
      onActionError(actionQueue, node, error);
    }
  }
}

2.6 非同期アクションの処理: handleActionReturnValue

// packages/react-reconciler/src/ReactFiberHooks.js

function handleActionReturnValue<S, P>(
  actionQueue: ActionStateQueue<S, P>,
  node: ActionStateQueueNode<S, P>,
  returnValue: mixed,
) {
  if (
    returnValue !== null &&
    typeof returnValue === 'object' &&
    typeof returnValue.then === 'function'
  ) {
    const thenable = ((returnValue: any): Thenable<Awaited<S>>);
    // Attach a listener to read the return state of the action. As soon as
    // this resolves, we can run the next action in the sequence.
    thenable.then(
      (nextState: Awaited<S>) => {
        onActionSuccess(actionQueue, node, nextState);
      },
      (error: mixed) => onActionError(actionQueue, node, error),
    );
  } else {
    const nextState = ((returnValue: any): Awaited<S>);
    onActionSuccess(actionQueue, node, nextState);
  }
}

💡 Promise の処理
アクションが Promise を返す場合、then を使って解決を待ちます。解決後に onActionSuccess が呼ばれ、次のアクションがキューから取り出されて実行されます。

2.7 アクション成功時の処理: onActionSuccess

// packages/react-reconciler/src/ReactFiberHooks.js

function onActionSuccess<S, P>(
  actionQueue: ActionStateQueue<S, P>,
  actionNode: ActionStateQueueNode<S, P>,
  nextState: Awaited<S>,
) {
  // The action finished running.
  actionNode.status = 'fulfilled';
  actionNode.value = nextState;
  notifyActionListeners(actionNode);

  actionQueue.state = nextState;

  // Pop the action from the queue and run the next pending action, if there
  // are any.
  const last = actionQueue.pending;
  if (last !== null) {
    const first = last.next;
    if (first === last) {
      // This was the last action in the queue.
      actionQueue.pending = null;
    } else {
      // Remove the first node from the circular queue.
      const next = first.next;
      last.next = next;

      // Run the next action.
      runActionStateAction(actionQueue, next);
    }
  }
}

2.8 更新時の処理: updateActionState

// packages/react-reconciler/src/ReactFiberHooks.js

function updateActionState<S, P>(
  action: (Awaited<S>, P) => S,
  initialState: Awaited<S>,
  permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
  const stateHook = updateWorkInProgressHook();
  const currentStateHook = ((currentHook: any): Hook);
  return updateActionStateImpl(
    stateHook,
    currentStateHook,
    action,
    initialState,
    permalink,
  );
}

function updateActionStateImpl<S, P>(
  stateHook: Hook,
  currentStateHook: Hook,
  action: (Awaited<S>, P) => S,
  initialState: Awaited<S>,
  permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
  const [actionResult] = updateReducerImpl<S | Thenable<S>, S | Thenable<S>>(
    stateHook,
    currentStateHook,
    actionStateReducer,
  );

  const [isPending] = updateState(false);

  // This will suspend until the action finishes.
  let state: Awaited<S>;
  if (
    typeof actionResult === 'object' &&
    actionResult !== null &&
    typeof actionResult.then === 'function'
  ) {
    try {
      state = useThenable(((actionResult: any): Thenable<Awaited<S>>));
    } catch (x) {
      if (x === SuspenseException) {
        throw SuspenseActionException;
      } else {
        throw x;
      }
    }
  } else {
    state = (actionResult: any);
  }

  const actionQueueHook = updateWorkInProgressHook();
  const actionQueue = actionQueueHook.queue;
  const dispatch = actionQueue.dispatch;

  // Check if a new action was passed. If so, update it in an effect.
  const prevAction = actionQueueHook.memoizedState;
  if (action !== prevAction) {
    currentlyRenderingFiber.flags |= PassiveEffect;
    pushSimpleEffect(
      HookHasEffect | HookPassive,
      createEffectInstance(),
      actionStateActionEffect.bind(null, actionQueue, action),
      null,
    );
  }

  return [state, dispatch, isPending];
}

2.9 内部の流れ図

2.10 まとめ: useActionState の内部構造

useActionState が動く仕組みの5ステージ

  1. mountActionState: 3つの内部 Hook(state, pending, actionQueue)を作成
  2. dispatchActionState: アクションノードを作成しキューに追加
  3. runActionStateAction: トランジションコンテキストを復元してアクション実行
  4. handleActionReturnValue: Promise の解決を待ち、結果を処理
  5. onActionSuccess: state を更新し、次のキューを実行

useState との違い

項目 useState useActionState
内部 Hook 数 1 3(state + pending + queue)
レンダーフェーズ更新 ✅ サポート ❌ 禁止
非同期対応 ❌ 手動で管理 ✅ 自動で Promise 処理
順次実行 N/A ✅ キューで管理
pending 状態 ❌ 手動で管理 ✅ 自動で追跡

3. 代表的ユースケース

3.1 基本的なフォーム送信

import { useActionState } from 'react';

async function submitForm(prevState, formData) {
  const name = formData.get('name');
  
  // サーバーにデータを送信
  const response = await fetch('/api/submit', {
    method: 'POST',
    body: JSON.stringify({ name }),
  });
  
  if (!response.ok) {
    return { error: '送信に失敗しました' };
  }
  
  return { success: true, message: `${name}さん、登録完了!` };
}

function RegistrationForm() {
  const [state, formAction, isPending] = useActionState(submitForm, null);
  
  return (
    <form action={formAction}>
      <input name="name" placeholder="名前" required />
      <button type="submit" disabled={isPending}>
        {isPending ? '送信中...' : '登録'}
      </button>
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">{state.message}</p>}
    </form>
  );
}

3.2 カウンター(シンプルな例)

import { useActionState } from 'react';

async function increment(prevState, formData) {
  return prevState + 1;
}

function Counter() {
  const [count, formAction, isPending] = useActionState(increment, 0);
  
  return (
    <form>
      <p>カウント: {count}</p>
      <button formAction={formAction} disabled={isPending}>
        +1
      </button>
    </form>
  );
}

3.3 フォームエラーの表示

import { useActionState } from 'react';

async function addToCart(prevState, formData) {
  const itemId = formData.get('itemId');
  
  try {
    await fetch(`/api/cart/add/${itemId}`, { method: 'POST' });
    return { message: 'カートに追加しました!' };
  } catch (error) {
    return { error: 'カートへの追加に失敗しました' };
  }
}

function AddToCartButton({ itemId, itemTitle }) {
  const [state, formAction, isPending] = useActionState(addToCart, null);
  
  return (
    <form action={formAction}>
      <h3>{itemTitle}</h3>
      <input type="hidden" name="itemId" value={itemId} />
      <button type="submit" disabled={isPending}>
        {isPending ? '追加中...' : 'カートに追加'}
      </button>
      {state?.error && <p className="error">{state.error}</p>}
      {state?.message && <p className="success">{state.message}</p>}
    </form>
  );
}

3.4 サーバーアクションとの連携

// actions.js (Server Actions)
'use server';

export async function createUser(prevState, formData) {
  const email = formData.get('email');
  const password = formData.get('password');
  
  // バリデーション
  if (!email || !password) {
    return { error: '全ての項目を入力してください' };
  }
  
  // データベースに保存
  try {
    await db.user.create({ email, password });
    return { success: true };
  } catch (error) {
    return { error: 'ユーザー作成に失敗しました' };
  }
}

// SignupForm.jsx (Client Component)
'use client';

import { useActionState } from 'react';
import { createUser } from './actions';

function SignupForm() {
  const [state, formAction, isPending] = useActionState(createUser, null);
  
  return (
    <form action={formAction}>
      <input name="email" type="email" placeholder="メールアドレス" />
      <input name="password" type="password" placeholder="パスワード" />
      <button disabled={isPending}>
        {isPending ? '登録中...' : 'アカウント作成'}
      </button>
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p>登録完了!</p>}
    </form>
  );
}

3.5 バリデーション付きフォーム

import { useActionState } from 'react';

async function validateAndSubmit(prevState, formData) {
  const errors = {};
  
  const name = formData.get('name');
  const email = formData.get('email');
  const age = formData.get('age');
  
  // バリデーション
  if (!name || name.length < 2) {
    errors.name = '名前は2文字以上で入力してください';
  }
  if (!email || !email.includes('@')) {
    errors.email = '有効なメールアドレスを入力してください';
  }
  if (!age || isNaN(age) || age < 0) {
    errors.age = '有効な年齢を入力してください';
  }
  
  if (Object.keys(errors).length > 0) {
    return { errors, values: { name, email, age } };
  }
  
  // 送信処理
  await submitToServer({ name, email, age });
  return { success: true };
}

function ValidatedForm() {
  const [state, formAction, isPending] = useActionState(validateAndSubmit, {
    errors: {},
    values: {},
  });
  
  return (
    <form action={formAction}>
      <div>
        <input
          name="name"
          placeholder="名前"
          defaultValue={state.values?.name}
        />
        {state.errors?.name && <span className="error">{state.errors.name}</span>}
      </div>
      <div>
        <input
          name="email"
          type="email"
          placeholder="メールアドレス"
          defaultValue={state.values?.email}
        />
        {state.errors?.email && <span className="error">{state.errors.email}</span>}
      </div>
      <div>
        <input
          name="age"
          type="number"
          placeholder="年齢"
          defaultValue={state.values?.age}
        />
        {state.errors?.age && <span className="error">{state.errors.age}</span>}
      </div>
      <button disabled={isPending}>送信</button>
      {state.success && <p className="success">送信完了!</p>}
    </form>
  );
}

4. パフォーマンスと注意点

4.1 アクション関数のシグネチャに注意

// ❌ 通常のフォームアクションと同じシグネチャを使用
function wrongAction(formData) {
  // formData が undefined になる!
  const name = formData.get('name');
}

// ✅ 第1引数は prevState
function correctAction(prevState, formData) {
  const name = formData.get('name');
  return { ...prevState, name };
}

4.2 レンダー中の更新は禁止

// ❌ レンダー中にアクションを呼び出す
function BadComponent() {
  const [state, formAction] = useActionState(action, null);
  
  // これはエラーになる
  formAction(new FormData());  // Error: Cannot update form state while rendering.
  
  return <div>{state}</div>;
}

// ✅ イベントハンドラ内で呼び出す
function GoodComponent() {
  const [state, formAction] = useActionState(action, null);
  
  return (
    <form action={formAction}>
      <button type="submit">送信</button>
    </form>
  );
}

4.3 非同期アクションはトランジション内で

// ⚠️ 警告が出る可能性
async function asyncAction(prevState, formData) {
  await fetch('/api/submit');
  return { success: true };
}

// formAction を直接呼び出すと警告
button.addEventListener('click', () => {
  formAction(formData);  // 警告: アクションがトランジション外で呼び出された
});

// ✅ form の action として使用するか startTransition で囲む
<form action={formAction}>
  <button type="submit">送信</button>
</form>

4.4 アクションは順次実行される

// 複数のアクションが短時間にディスパッチされた場合
formAction(data1);
formAction(data2);
formAction(data3);

// 実行順序は保証される: data1 → data2 → data3
// 各アクションは前のアクションが完了してから実行される

5. トラブルシューティング

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

原因: useActionState でラップすると、引数の順序が変わる

// ❌ 第1引数が formData だと思っている
function action(formData) {
  const name = formData.get('name');  // エラー!
}

// ✅ 第1引数は prevState、第2引数が formData
function action(prevState, formData) {
  const name = formData.get('name');  // OK
  return { name };
}

5.2 isPending が更新されない

原因: 同期アクションを使用している、またはトランジション外で呼び出している

// ❌ 同期アクション(isPending がすぐ false に戻る)
function syncAction(prevState, formData) {
  return prevState + 1;
}

// ✅ 非同期アクション
async function asyncAction(prevState, formData) {
  await new Promise(r => setTimeout(r, 1000));  // 待機
  return prevState + 1;
}

5.3 state が更新されない

原因: アクションから値を返していない

// ❌ return がない
async function badAction(prevState, formData) {
  await submitData(formData);
  // return がない → state は undefined になる
}

// ✅ 必ず値を返す
async function goodAction(prevState, formData) {
  await submitData(formData);
  return { success: true };
}

5.4 エラー後にアクションが実行されない

原因: エラーが発生すると actionQueue.actionnull になり、後続のアクションは実行されない

// エラー発生後は action が null になる
// 新しいアクションをディスパッチしても無視される

// 解決策: 再マウントするか、エラーハンドリングを適切に行う
async function safeAction(prevState, formData) {
  try {
    const result = await submitData(formData);
    return { success: true, data: result };
  } catch (error) {
    // エラーをスローせず、state で返す
    return { error: error.message };
  }
}

6. まとめ

この記事で解説した内容は、公式ドキュメントと facebook/react リポジトリに基づいています:

  • useActionState はフォームアクションの結果に基づいて state を更新するフック
  • 内部的には3つの Hook(state, pending, actionQueue)を組み合わせて実装
  • アクションは循環リンクリストのキューで順次実行される
  • 非同期アクションは Promise として処理され、完了を待ってから次のアクションを実行
  • isPending でアクションの進行状態を自動追跡

使い分けの指針: フォーム送信やアクションの結果に基づいて state を更新したい場合は useActionState、単純な状態管理には useState を使用。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?