Artículos
NextJs Validación Formularios usando Conform

Conform - SSD Validacion Formularios

Requisitos

A la fecha 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

Con estos el codigo utiliza:

import { useActionForm } from "react"

React 18

next: 15
react: 18

Con estos el codigo utiliza:

import { useFormState } from "react-dom"

Instalar zod y conform

Zod sirve para crear esquemas (schema) entypescript y conform es el utilitario para validar formularios www.conform.guide

# Conform
npm install @conform-to/react @conform-to/zod --legacy-peer-deps
 
#Zod
npm i zop --legacy-peer-deps
 

Crear Schema Zod

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),
  
});

Esquema alternativo con mesnajes personalizados

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'),
    
  ),
 
});

Crear 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");
}

Crear Formulario

Formulario sin configuracion

"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>
    )
}

Configurar Conform en el formulario

  1. Importar useActionForm - react 19 o useFormState
  2. Importar dependecnias
  3. Configurar useFormState ...
  4. Reemplazar la action x conform: useForm
  5. Actualizar Inputs con: key, name y defaultValue
  6. Agregar mensajes a 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">

            {/* Agregar id al form, actualizar llamada a 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>
                            
                            {/* Actualizar key,name y defaultValue */}
                            <Input id="names" 
                                key={fields.names.key}
                                name="{fields.names.name}"
                                defaultValue={fields.names.initialValue}
                                placeholder="Primer nombre, segundo" />
                            
                            {/* Actualizar despliegue de errores/mensajes */}
                            <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>
    )
}