safe-action-validation-errors
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesenext-safe-action Validation Errors
next-safe-action 验证错误处理
Two Sources of Validation Errors
两类验证错误来源
- Schema validation — automatic when input doesn't match
.inputSchema() - Manual validation — via in server code (e.g., "email already taken")
returnValidationErrors()
Both produce the same error structure on the client.
- Schema验证——当输入不符合时自动触发
.inputSchema() - 手动验证——通过服务端代码中的实现(例如“邮箱已被占用”)
returnValidationErrors()
这两类错误在客户端会生成相同的错误结构。
Default Error Shape (Formatted)
默认错误结构(格式化形式)
Mirrors the schema structure with arrays at each level:
_errorsts
// For schema: z.object({ email: z.string().email(), address: z.object({ city: z.string() }) })
{
_errors: ["Form-level error"], // root errors
email: { _errors: ["Invalid email address"] }, // field errors
address: {
_errors: ["Address section error"],
city: { _errors: ["City is required"] }, // nested field errors
},
}镜像Schema结构,每个层级都带有数组:
_errorsts
// For schema: z.object({ email: z.string().email(), address: z.object({ city: z.string() }) })
{
_errors: ["Form-level error"], // root errors
email: { _errors: ["Invalid email address"] }, // field errors
address: {
_errors: ["Address section error"],
city: { _errors: ["City is required"] }, // nested field errors
},
}returnValidationErrors
returnValidationErrors
Throws a that the framework catches and returns as . It never returns — it always throws.
ActionServerValidationErrorresult.validationErrorsts
"use server";
import { z } from "zod";
import { returnValidationErrors } from "next-safe-action";
import { actionClient } from "@/lib/safe-action";
const registerSchema = z.object({
email: z.string().email(),
username: z.string().min(3),
});
export const register = actionClient
.inputSchema(registerSchema)
.action(async ({ parsedInput }) => {
// Check business rules after schema validation passes
const existingUser = await db.user.findByEmail(parsedInput.email);
if (existingUser) {
returnValidationErrors(registerSchema, {
email: { _errors: ["This email is already registered"] },
});
}
const existingUsername = await db.user.findByUsername(parsedInput.username);
if (existingUsername) {
returnValidationErrors(registerSchema, {
username: { _errors: ["This username is taken"] },
});
}
// Both checks passed — create the user
const user = await db.user.create(parsedInput);
return { id: user.id };
});会抛出一个,框架会捕获该错误并将其作为返回。该方法不会返回值——它始终会抛出错误。
ActionServerValidationErrorresult.validationErrorsts
"use server";
import { z } from "zod";
import { returnValidationErrors } from "next-safe-action";
import { actionClient } from "@/lib/safe-action";
const registerSchema = z.object({
email: z.string().email(),
username: z.string().min(3),
});
export const register = actionClient
.inputSchema(registerSchema)
.action(async ({ parsedInput }) => {
// Check business rules after schema validation passes
const existingUser = await db.user.findByEmail(parsedInput.email);
if (existingUser) {
returnValidationErrors(registerSchema, {
email: { _errors: ["This email is already registered"] },
});
}
const existingUsername = await db.user.findByUsername(parsedInput.username);
if (existingUsername) {
returnValidationErrors(registerSchema, {
username: { _errors: ["This username is taken"] },
});
}
// Both checks passed — create the user
const user = await db.user.create(parsedInput);
return { id: user.id };
});Root-Level Errors
根层级错误
Use at the top level for form-wide errors:
_errorsts
returnValidationErrors(schema, {
_errors: ["You can only create 5 posts per day"],
});在顶层使用来定义表单级别的全局错误:
_errorsts
returnValidationErrors(schema, {
_errors: ["You can only create 5 posts per day"],
});Supporting Docs
相关文档
- Custom validation errors and returnValidationErrors patterns
- Formatted vs flattened shapes, per-action override
- Custom validation errors and returnValidationErrors patterns
- Formatted vs flattened shapes, per-action override
Displaying Validation Errors
验证错误的展示
tsx
// Formatted shape (default)
{result.validationErrors?.email?._errors?.map((error) => (
<p key={error} className="text-red-500">{error}</p>
))}
// Root-level errors
{result.validationErrors?._errors?.map((error) => (
<p key={error} className="text-red-500">{error}</p>
))}tsx
// Flattened shape
{result.validationErrors?.fieldErrors?.email?.map((error) => (
<p key={error} className="text-red-500">{error}</p>
))}
// Form-level errors (flattened)
{result.validationErrors?.formErrors?.map((error) => (
<p key={error} className="text-red-500">{error}</p>
))}tsx
// Formatted shape (default)
{result.validationErrors?.email?._errors?.map((error) => (
<p key={error} className="text-red-500">{error}</p>
))}
// Root-level errors
{result.validationErrors?._errors?.map((error) => (
<p key={error} className="text-red-500">{error}</p>
))}tsx
// Flattened shape
{result.validationErrors?.fieldErrors?.email?.map((error) => (
<p key={error} className="text-red-500">{error}</p>
))}
// Form-level errors (flattened)
{result.validationErrors?.formErrors?.map((error) => (
<p key={error} className="text-red-500">{error}</p>
))}