Next.js example

RVF can also be used in Next.js projects to have full-stack form validation and type safety using the app router.

// app/users/create/actions.tsx
'use server'

import { schema } from "@/app/users/create/schema";
import prisma from "@/lib/prisma";
import { revalidatePath } from "next/cache";
import { parseFormData } from "@rvf/react";

export async function createUser(
  initialState: unknown,
  formData: FormData
) {
  const result = await parseFormData(formData, schema);

  if (result.error) {
    return result.error;
  }

  const { firstName, lastName } = result.data;

  const existingUser = await prisma.user.findFirst({
    where: {
      lastName,
    },
  });
  if (existingUser) {
    return {
      fieldErrors: {
        lastName: "A user with this last name already exists",
      },
    };
  }

  await prisma.user.create({
    data: {
      firstName,
      lastName,
    },
  });

  revalidatePath("/users");
}
// app/users/create/page.tsx
'use client';
import { useForm } from "@rvf/react";
import { Button } from "@mui/material";
import { schema } from "@/app/users/create/schema";
import { createUser } from "@/app/users/create/actions";
import { ValidatedTextField } from "@/components/validated-text-field";
import { useActionState, useTransition } from "react";

export default function CreateUserPage() {
  const [lastResult, action] = useActionState(createUser, undefined);
  const [_, startTransition] = useTransition();
  const form = useForm({
    schema,
    defaultValues: {
      firstName: "",
      lastName: "",
    },
    handleSubmit: async (_, formData) => {
      startTransition(() => {
        action(formData);
      });
    },
    serverValidationErrors: lastResult?.fieldErrors,
  });

  return (
    <form {...form.getFormProps()}>
      <label>
        First name
        <input name="firstName" />
      </label>

      <label>
        Last name
        <input name="lastName" />
      </label>
      <button type="submit">Submit</button>
    </form>
  );
}
// app/users/create/schema.tsx
import { z } from "zod";

export const schema = z.object({
  firstName: z.string().min(2).max(100),
  lastName: z.string().min(2).max(100),
});

export type User = z.infer<typeof schema>;