I have ditched the createServerValidate altogether and used next-safe-actions.
create-form.tsx
import { create } from "./_action";
export const CreateForm = () => {
const form = useAppForm({
defaultValues: {
name: "",
completed: false,
category: "" as TodoCategory,
},
validators: {
onSubmitAsync: async ({ value }) => {
const result = await create(value);
if (result?.validationErrors) {
return {
fields: formatValidationErrors(result.validationErrors),
};
}
if (result?.serverError) {
return {
form: result.serverError,
fields: {},
};
}
if (result?.data?.success) {
toast.success(result?.data?.success);
}
return null;
},
},
onSubmit: () => {
redirect("/cms/todos");
},
});
return ()
}
_action.ts
"use server";
import { nanoid } from "nanoid";
import { actionClient } from "@/lib/safe-action";
import { db } from "@/server/db";
import { todo } from "@/server/db/schema";
import { createSchema } from "./_validation-schema";
export const create = actionClient
.schema(createSchema)
.action(async ({ parsedInput: { name, completed, category } }) => {
try {
await db.insert(todo).values({
id: `todo_${nanoid()}`,
name,
completed,
category: category,
});
} catch (error) {
throw error;
}
return { success: "Successfully created todo" };
});
_validation-schema.ts
import { z } from "zod";
import { TodoCategory } from "@/server/db/schema";
export const createSchema = z.object({
name: z
.string()
.min(1, "Name is required")
.max(36, "Maximum of 36 characters"),
completed: z.boolean(),
category: z.nativeEnum(TodoCategory, {
errorMap: () => ({ message: "Category is required" }),
}),
});