#onServerValidate example lacking other patterns.

1 messages · Page 1 of 1 (latest)

gilded sorrel
#

I don't understand why we have formErrors.map but on onServerValidate but the example only returns 1 error message.

const serverValidate = createServerValidate({
  ...formOpts,
  onServerValidate: ({ value }) => {
    if (value.age < 12) {
      return 'Server validation: You must be at least 12 to sign up'
    }
  },
})

   {formErrors.map((error) => (
        <p key={error as string}>{error}</p>
      ))}

Is there a way to output it in a field? It would be great to have an example where we validate each field in server validation and then output the field errors.

An example also with standard schema validate with custom db validation logic.

round heron
#

This is something I would like also.

round heron
#

I can help a little, now. The server validation happens on submission. The return can be anything, including a simple string, but if you wanted to check several things (eg, a password fitting the rules) then you might return several issues. After that, the response should be merged to formErrors with the useActionState, which means client-side checks that might have left messages will also be present. The map allows you to show them all.

gilded sorrel
#

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" }),
  }),
});
ornate adder
scenic talon
#

formatValidationErrors

what is this function sr?

round heron
#

import { formatValidationErrors } from "next-safe-action";

gilded sorrel
#

It's a custom function I forgot to include in the initial message. I didn't use the one from next-safe-action. I'm not sure if they're identical. 😄

export function formatValidationErrors(
  validationErrors: ValidationErrors | null | undefined,
): FormattedErrors {
  if (!validationErrors) return {};

  const formattedErrors: FormattedErrors = {};

  // Process each field in the validation errors
  Object.entries(validationErrors).forEach(([field, value]) => {
    // Skip the root _errors field as it's not associated with a specific form field
    if (field === "_errors") return;

    // Handle the nested structure
    if (
      value &&
      typeof value === "object" &&
      "_errors" in value &&
      Array.isArray(value._errors)
    ) {
      if (value._errors.length > 0) {
        formattedErrors[field] = value._errors.map((message) => ({ message }));
      }
    }
  });

  return formattedErrors;
}