safe-action-hooks

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

next-safe-action React Hooks

next-safe-action React Hooks

Import

导入

ts
// Standard hooks
import { useAction, useOptimisticAction } from "next-safe-action/hooks";

// Deprecated — use React's useActionState directly instead
import { useStateAction } from "next-safe-action/stateful-hooks";
ts
// Standard hooks
import { useAction, useOptimisticAction } from "next-safe-action/hooks";

// Deprecated — use React's useActionState directly instead
import { useStateAction } from "next-safe-action/stateful-hooks";

useAction — Quick Start

useAction — 快速开始

tsx
"use client";

import { useAction } from "next-safe-action/hooks";
import { createUser } from "@/app/actions";

export function CreateUserForm() {
  const { execute, result, status, isExecuting, isPending } = useAction(createUser, {
    onSuccess: ({ data }) => {
      console.log("User created:", data);
    },
    onError: ({ error }) => {
      console.error("Failed:", error.serverError);
    },
  });

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const formData = new FormData(e.currentTarget);
      execute({ name: formData.get("name") as string });
    }}>
      <input name="name" required />
      <button type="submit" disabled={isPending}>
        {isPending ? "Creating..." : "Create User"}
      </button>
      {result.serverError && <p className="error">{result.serverError}</p>}
      {result.data && <p className="success">Created: {result.data.id}</p>}
    </form>
  );
}
tsx
"use client";

import { useAction } from "next-safe-action/hooks";
import { createUser } from "@/app/actions";

export function CreateUserForm() {
  const { execute, result, status, isExecuting, isPending } = useAction(createUser, {
    onSuccess: ({ data }) => {
      console.log("User created:", data);
    },
    onError: ({ error }) => {
      console.error("Failed:", error.serverError);
    },
  });

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const formData = new FormData(e.currentTarget);
      execute({ name: formData.get("name") as string });
    }}>
      <input name="name" required />
      <button type="submit" disabled={isPending}>
        {isPending ? "Creating..." : "Create User"}
      </button>
      {result.serverError && <p className="error">{result.serverError}</p>}
      {result.data && <p className="success">Created: {result.data.id}</p>}
    </form>
  );
}

useOptimisticAction — Quick Start

useOptimisticAction — 快速开始

tsx
"use client";

import { useOptimisticAction } from "next-safe-action/hooks";
import { toggleTodo } from "@/app/actions";

export function TodoItem({ todo }: { todo: Todo }) {
  const { execute, optimisticState } = useOptimisticAction(toggleTodo, {
    currentState: todo,
    updateFn: (state, input) => ({
      ...state,
      completed: !state.completed,
    }),
  });

  return (
    <label>
      <input
        type="checkbox"
        checked={optimisticState.completed}
        onChange={() => execute({ todoId: todo.id })}
      />
      {todo.title}
    </label>
  );
}
tsx
"use client";

import { useOptimisticAction } from "next-safe-action/hooks";
import { toggleTodo } from "@/app/actions";

export function TodoItem({ todo }: { todo: Todo }) {
  const { execute, optimisticState } = useOptimisticAction(toggleTodo, {
    currentState: todo,
    updateFn: (state, input) => ({
      ...state,
      completed: !state.completed,
    }),
  });

  return (
    <label>
      <input
        type="checkbox"
        checked={optimisticState.completed}
        onChange={() => execute({ todoId: todo.id })}
      />
      {todo.title}
    </label>
  );
}

Return Value

返回值

Both
useAction
and
useOptimisticAction
return:
PropertyTypeDescription
execute(input)
(input) => void
Fire-and-forget execution
executeAsync(input)
(input) => Promise<Result>
Returns a promise with the result
input
Input | undefined
Last input passed to execute
result
SafeActionResult
Last action result (
{ data?, serverError?, validationErrors? }
)
reset()
() => void
Resets all state to initial values
status
HookActionStatus
Current status string
isIdle
boolean
No execution has started yet
isExecuting
boolean
Action promise is pending
isTransitioning
boolean
React transition is pending
isPending
boolean
isExecuting || isTransitioning
hasSucceeded
boolean
Last execution returned data
hasErrored
boolean
Last execution had an error
hasNavigated
boolean
Last execution triggered a navigation
useOptimisticAction
additionally returns: |
optimisticState
|
State
| The optimistically-updated state |
useAction
useOptimisticAction
均返回以下属性:
属性类型描述
execute(input)
(input) => void
触发即忘式执行
executeAsync(input)
(input) => Promise<Result>
返回携带执行结果的Promise
input
Input | undefined
最后一次传入execute的输入参数
result
SafeActionResult
最后一次操作的结果 (
{ data?, serverError?, validationErrors? }
)
reset()
() => void
将所有状态重置为初始值
status
HookActionStatus
当前状态字符串
isIdle
boolean
尚未触发任何执行
isExecuting
boolean
操作Promise处于pending状态
isTransitioning
boolean
React过渡处于pending状态
isPending
boolean
等价于
isExecuting || isTransitioning
hasSucceeded
boolean
最后一次执行返回了数据
hasErrored
boolean
最后一次执行出现错误
hasNavigated
boolean
最后一次执行触发了页面跳转
useOptimisticAction
额外返回: |
optimisticState
|
State
| 经过乐观更新的状态 |

Supporting Docs

相关文档

  • execute vs executeAsync, result handling
  • Optimistic updates with useOptimisticAction
  • Status lifecycle and all callbacks
  • execute与executeAsync对比、结果处理
  • 使用useOptimisticAction实现乐观更新
  • 状态生命周期与全部回调

Anti-Patterns

反面模式

ts
// BAD: Using executeAsync without try/catch when navigation errors are possible
const handleClick = async () => {
  const result = await executeAsync({ id }); // Throws on redirect!
  showToast(result.data);
};

// GOOD: Wrap executeAsync in try/catch
const handleClick = async () => {
  try {
    const result = await executeAsync({ id });
    showToast(result.data);
  } catch (e) {
    // Navigation errors (redirect, notFound) are re-thrown
    // They'll be handled by Next.js — just let them propagate
    throw e;
  }
};
ts
// BAD: Using executeAsync without try/catch when navigation errors are possible
const handleClick = async () => {
  const result = await executeAsync({ id }); // Throws on redirect!
  showToast(result.data);
};

// GOOD: Wrap executeAsync in try/catch
const handleClick = async () => {
  try {
    const result = await executeAsync({ id });
    showToast(result.data);
  } catch (e) {
    // Navigation errors (redirect, notFound) are re-thrown
    // They'll be handled by Next.js — just let them propagate
    throw e;
  }
};