Conform - SSD Form Validation
Requisitos
Date: 14/11/2024
https://www.youtube.com/watch?v=SJjP7zdYv_s&t=908s (opens in a new tab)
React 19 (canary)
next: 15
react 19.rc
We use this imports:
import { useActionForm } from "react"
React 18
next: 15
react: 18
We use this imports:
import { useFormState } from "react-dom"
Install zod and conform
Zod is used to create schemas in TypeScript, and Conform is the utility for validating forms.www.conform.guide
# Conform
npm install @conform-to/react @conform-to/zod --legacy-peer-deps
#Zod
npm i zop --legacy-peer-deps
Create Zod Schema
import { z } from "zod";
export const personSchema = z.object({
uuid: z.string().uuid(), // UUID type validation
names: z.string().min(2).max(80),
lastnameFirst: z.string().min(2).max(60),
});
Alternative schema with personalized messages
export const personValidationSchema = z.object({
uuid: z.string().uuid(), // UUID type validation
names: z.preprocess(
(value) =>(value === '' ? undefined : value),
z.string({ required_error: "Nombres es obligatorio" })
.min(2, 'Nombres debe tener al menos 2 caracteres'),
),
});
Create Server Side Validation - action
"use server";
import { parseWithZod } from "@conform-to/zod";
import { redirect } from "next/navigation";
import { personSchema } from "../data/schema";
export async function registerPersonas (prevState: unknown, formData: FormData){
const submission = parseWithZod(formData, {
schema: personSchema,
});
if(submission.status !== 'success'){
return submission.reply();
}
return redirect("/dashboard/personas");
}
Create Form
Form with NO configuration
"use client"
import { Button } from "@/components/ui/button"
import {Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export default function NewPersona() {
return (
<div className="col-span-2 grid items-start gap-6 lg:col-span-1">
<form className="grid gap-6" action={...} >
<Card className="">
<CardHeader>
<CardTitle>Registrar persona</CardTitle>
<CardDescription>
Complete el formulario para continuar
</CardDescription>
</CardHeader>
<CardContent className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="names">Nombres</Label>
<Input placeholder="Primer nombre, segundo" />
</div>
<div className="grid gap-2">
<Label htmlFor="subject">Apellido Paterno</Label>
<Input placeholder="Apellido paterno o primer apellido" />
</div>
</CardContent>
<CardFooter className="justify-between space-x-2">
<Button variant="ghost">Cancelar</Button>
<Button type="submit">Guardar</Button>
</CardFooter>
</Card>
</form>
</div>
)
}
Setting Up Conform in the Form
- Import
useActionForm
for React 19 oruseFormState
. - Import dependencies.
- Configure
useFormState
. - Replace
action
with Conform’suseForm
. - Update inputs with:
key
,name
, anddefaultValue
. - Add messages to inputs.
//otros imports
import { registerPersonas } from "./personsa_actions"
import { useFormState } from "react-dom"
import { useForm } from "@conform-to/react"
import { parseWithZod } from "@conform-to/zod"
import { personSchema } from "../data/schema"
export default function NewPersona() {
const [lastResult, action] = useFormState(registerPersonas, undefined);
const [form, fields] = useForm({
lastResult,
onValidate({formData}){
return parseWithZod(formData, {
schema: personSchema
})
},
shouldValidate: 'onBlur',
shouldRevalidate: 'onInput',
});
return (
<div className="col-span-2 grid items-start gap-6 lg:col-span-1">
{/* Add to form, update form action: onSubmit */}
<form className="grid gap-6" id={form.id} action={action} onSubmit={form.onSubmit}>
<Card className="">
<CardHeader>
<CardTitle>Registrar persona</CardTitle>
<CardDescription>
Complete el formulario para continuar
</CardDescription>
</CardHeader>
<CardContent className="grid gap-6">
<div className="grid gap-2">
<Label htmlFor="names">Nombres</Label>
{/* Update key,name y defaultValue */}
<Input id="names"
key={fields.names.key}
name="{fields.names.name}"
defaultValue={fields.names.initialValue}
placeholder="Primer nombre, segundo" />
{/* Update messages and errors */}
<p className="text-xs text-muted-foreground text-red-500">{fields.names.errors}</p>
</div>
<div className="grid gap-2">
<Label htmlFor="subject">Apellido Paterno</Label>
<Input id="subject"
key={fields.lastnameFirst.key}
name="{fields.lastnameFirst.name}"
defaultValue={fields.lastnameFirst.initialValue}
placeholder="Apellido paterno o primer apellido" />
<p className="text-xs text-muted-foreground text-red-500">{fields.lastnameFirst.errors}</p>
</div>
</CardContent>
<CardFooter className="justify-between space-x-2">
<Button variant="ghost">Cancelar</Button>
<Button type="submit">Guardar</Button>
</CardFooter>
</Card>
</form>
</div>
)
}