mcpField
S
safe-action-tanstack-query

safe-action-tanstack-query

github.com/next-safe-action开发1447 次安装9 周前更新
代码仓库

关于 safe-action-tanstack-query

safe-action-tanstack-query 是将 next-safe-action 的 Server Actions 接入 TanStack Query mutation 的适配器,适用于已使用 TanStack Query 的 Next.js 项目。它将结果信封中的服务端或校验错误转换为带类型的错误。

主要功能

  1. 01生成 TanStack Query mutation 选项
  2. 02将服务端与校验错误转为 ActionMutationError
  3. 03提供错误类型判断工具
  4. 04支持重试、缓存失效与离线 mutation

适用场景

  1. 01在 TanStack Query 项目中调用 Server Actions
  2. 02根据 mutation 结果失效客户端查询缓存
  3. 03为失败的 mutation 配置重试
  4. 04持久化离线期间暂停的 mutation

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/next-safe-action/skills --skill safe-action-tanstack-query

安装后在 Claude Code 中输入/safe-action-tanstack-query确认已加载。

附带文件mutation-patterns.md

详细介绍

next-safe-action TanStack Query 适配器

安装

npm install @next-safe-action/adapter-tanstack-query @tanstack/react-query

导入

import {
  mutationOptions,
  ActionMutationError,
  isActionMutationError,
  hasServerError,
  hasValidationErrors,
} from "@next-safe-action/adapter-tanstack-query";

快速开始

"use client";

import { useMutation } from "@tanstack/react-query";
import { mutationOptions } from "@next-safe-action/adapter-tanstack-query";
import { createUser } from "@/app/actions";

export function CreateUserForm() {
  const { mutate, isPending, isError, error, data } = useMutation(
    mutationOptions(createUser, {
      onSuccess: (data) => {
        toast.success(`Created ${data.name}`);
      },
    })
  );

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      const fd = new FormData(e.currentTarget);
      mutate({ name: fd.get("name") as string, email: fd.get("email") as string });
    }}>
      <input name="name" required />
      <input name="email" type="email" required />
      <button type="submit" disabled={isPending}>
        {isPending ? "Creating..." : "Create User"}
      </button>
      {isError && <p className="text-red-500">{error.message}</p>}
      {data && <p>Created: {data.name}</p>}
    </form>
  );
}

工作原理

mutationOptions() 会创建一个完整的 UseMutationOptions 对象,将 next-safe-action 的结果封装转换为 TanStack Query 的错误模型:

  1. 调用安全操作,并传入 mutate() / mutateAsync() 提供的输入
  2. 检查结果封装中是否存在 serverError 或 validationErrors
  3. 如果其中任一项存在,则抛出 ActionMutationError(一种客户端错误类,可使用 instanceof 判断)
  4. 成功时直接返回 data,作为 TanStack Query 的 TData
  5. 处理导航错误(redirect()、notFound() 等):组合 throwOnError,确保它们始终在 React 的渲染阶段重新抛出

通过 returnServerError() 生成的预期错误与其他服务器错误一样会进入 serverError,因此会以带有类型化载荷的 ActionMutationError 形式呈现,与 mutationOptions() 完全兼容(与 throwServerError 不同)。

该如何选择

场景建议
不使用 TanStack Query 的新 Next.js 项目内置 Hooks
简单的表单提交和按钮操作内置 Hooks
通过 React 的 useOptimistic 实现即时乐观 UI内置 Hooks(useOptimisticAction)
必须排队并累积的重叠写入(重新排序、看板)内置 Hooks(useOptimisticStateAction)
独立于用户而变化的数据(套接字、轮询、后台刷新)使用 TanStack Query / SWR 获取数据,使用适配器处理变更
不引入额外依赖内置 Hooks
已使用 TanStack Query 获取数据适配器
已使用 tRPC + TanStack Query适配器
需要自动重试和退避适配器
需要在变更后使客户端查询缓存失效适配器
希望使用 TanStack Query DevTools 查看变更适配器
需要离线变更持久化适配器

功能对比

功能内置 Hooks适配器
React Transitions是,操作在 startTransition 中运行否
乐观更新通过 React 的 useOptimistic 使用 useOptimisticAction通过 onMutate + 查询缓存手动实现
自动重试否是,使用带退避的 retry 选项
服务端缓存失效是,使用 revalidatePath() / revalidateTag()是,使用相同的 Next.js API
客户端查询缓存失效否(不适用)是,使用 queryClient.invalidateQueries()
DevTools否是,TanStack Query DevTools
错误模型结果封装(result.serverError、result.validationErrors)抛出带类型守卫的 ActionMutationError
离线变更持久化否是,通过 dehydrate/hydrate 暂停变更
异步执行executeAsync() 返回 Promise<Result>mutateAsync() 返回 Promise<Data>
状态跟踪status 字符串 + 简写布尔值布尔标志(isPending、isError、isSuccess)
额外依赖无(仅 React)@tanstack/react-query

**一般建议:**大多数 Next.js 应用优先使用内置 Hooks。它们无需额外依赖,并且能与 React 的并发渲染集成。如果技术栈中已包含 TanStack Query,尤其是需要缓存失效、重试、DevTools 和离线支持时,优先使用适配器。

为什么只支持变更

此适配器仅提供 mutationOptions(),这是有意为之,因此没有 queryOptions():

  • Server Actions 仅使用 POST,不适用于基于 GET 的查询
  • Server Actions 会按客户端排队,造成请求瀑布流
  • POST 请求会绕过浏览器缓存、ETag 和条件请求
  • TanStack Query 去重所需的稳定资源标识不存在

获取数据时:使用 Server Components(服务端)、Route Handlers + useQuery(客户端),或 tRPC(全栈类型安全)。

入口

入口导出环境
@next-safe-action/adapter-tanstack-querymutationOptions、ActionMutationError、isActionMutationError、hasServerError、hasValidationErrors、类型客户端

重要限制

仅适用于无状态的 .action() 函数。 mutationOptions() 接受 SingleInputActionFn,因此无法包装 .stateAction()。对于有状态的操作,请使用 next-safe-action/hooks 中的 useStateAction / useOptimisticStateAction。

仅适用于不会抛出异常的操作。 请勿对传递给 mutationOptions() 的操作使用 throwValidationErrors: true 或 throwServerError: true。适配器会检查结果封装中的错误。如果错误是抛出而不是返回的,适配器就无法提取结构化错误数据,你也会失去类型安全的错误处理能力。TypeScript 通过 NonThrowingActionConstraint 强制执行此限制。

相关文档

反模式

// BAD: Using throwValidationErrors with adapter — errors bypass the result envelope
const client = createSafeActionClient({ throwValidationErrors: true });
const action = client.inputSchema(schema).action(async ({ parsedInput }) => { ... });
mutationOptions(action); // TypeScript error! NonThrowingActionConstraint not met

// BAD: Using mutationOptions for data fetching — server actions are POST-only
const { data } = useQuery(mutationOptions(fetchUsers)); // Wrong! Use Route Handler + useQuery

// BAD: Manually calling the action inside mutationFn
useMutation({
  mutationFn: async (input) => {
    const result = await myAction(input); // Loses error bridging, navigation handling
    return result.data;
  },
});

// GOOD: Let mutationOptions handle the bridging
useMutation(mutationOptions(myAction));

评论

支持 Markdown