00
:
00
:
00
:
00
Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Integrazione con React Hook Form

Perche’ Usare Zod con React Hook Form

React Hook Form e’ una delle librerie piu’ popolari per la gestione dei form in React. Integrandola con Zod ottieni:

  • Validazione type-safe: Lo schema Zod definisce sia la validazione che il tipo TypeScript
  • Messaggi di errore personalizzati: Gestiti direttamente nello schema Zod
  • Un’unica fonte di verita’: Non devi duplicare le regole di validazione

Installazione

npm install react-hook-form @hookform/resolvers zod

Setup Base

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";

// 1. Definisci lo schema Zod
const LoginSchema = z.object({
  email: z.string()
    .min(1, "L'email e' obbligatoria")
    .email("Formato email non valido"),
  password: z.string()
    .min(8, "La password deve avere almeno 8 caratteri"),
});

// 2. Inferisci il tipo
type LoginForm = z.infer<typeof LoginSchema>;

// 3. Usa nel componente
function LoginPage() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<LoginForm>({
    resolver: zodResolver(LoginSchema),
  });

  const onSubmit = async (dati: LoginForm) => {
    // dati e' gia' validato e tipizzato
    console.log(dati.email, dati.password);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input {...register("email")} placeholder="Email" />
        {errors.email && <span>{errors.email.message}</span>}
      </div>
      <div>
        <input {...register("password")} type="password" placeholder="Password" />
        {errors.password && <span>{errors.password.message}</span>}
      </div>
      <button type="submit" disabled={isSubmitting}>
        Accedi
      </button>
    </form>
  );
}

Esempio Completo: Form di Registrazione

const RegistrazioneSchema = z.object({
  nome: z.string()
    .min(2, "Il nome deve avere almeno 2 caratteri")
    .max(50, "Il nome non puo' superare i 50 caratteri"),
  cognome: z.string()
    .min(2, "Il cognome deve avere almeno 2 caratteri"),
  email: z.string()
    .min(1, "L'email e' obbligatoria")
    .email("Email non valida"),
  eta: z.coerce
    .number({ invalid_type_error: "L'eta' deve essere un numero" })
    .int("L'eta' deve essere un numero intero")
    .min(13, "Devi avere almeno 13 anni")
    .max(120, "Eta' non valida"),
  password: z.string()
    .min(8, "Minimo 8 caratteri")
    .regex(/[A-Z]/, "Deve contenere almeno una maiuscola")
    .regex(/[0-9]/, "Deve contenere almeno un numero")
    .regex(/[^A-Za-z0-9]/, "Deve contenere almeno un carattere speciale"),
  confermaPassword: z.string(),
  accettaTermini: z.literal(true, {
    errorMap: () => ({ message: "Devi accettare i termini e le condizioni" }),
  }),
}).refine((dati) => dati.password === dati.confermaPassword, {
  message: "Le password non corrispondono",
  path: ["confermaPassword"],
});

type RegistrazioneForm = z.infer<typeof RegistrazioneSchema>;

function RegistrazionePage() {
  const {
    register,
    handleSubmit,
    formState: { errors, isSubmitting },
  } = useForm<RegistrazioneForm>({
    resolver: zodResolver(RegistrazioneSchema),
    defaultValues: {
      nome: "",
      cognome: "",
      email: "",
      password: "",
      confermaPassword: "",
    },
  });

  const onSubmit = async (dati: RegistrazioneForm) => {
    const response = await fetch("/api/registrazione", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(dati),
    });
    // gestisci la risposta...
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Nome</label>
        <input {...register("nome")} />
        {errors.nome && <p className="errore">{errors.nome.message}</p>}
      </div>

      <div>
        <label>Cognome</label>
        <input {...register("cognome")} />
        {errors.cognome && <p className="errore">{errors.cognome.message}</p>}
      </div>

      <div>
        <label>Email</label>
        <input {...register("email")} type="email" />
        {errors.email && <p className="errore">{errors.email.message}</p>}
      </div>

      <div>
        <label>Eta'</label>
        <input {...register("eta")} type="number" />
        {errors.eta && <p className="errore">{errors.eta.message}</p>}
      </div>

      <div>
        <label>Password</label>
        <input {...register("password")} type="password" />
        {errors.password && <p className="errore">{errors.password.message}</p>}
      </div>

      <div>
        <label>Conferma Password</label>
        <input {...register("confermaPassword")} type="password" />
        {errors.confermaPassword && (
          <p className="errore">{errors.confermaPassword.message}</p>
        )}
      </div>

      <div>
        <label>
          <input {...register("accettaTermini")} type="checkbox" />
          Accetto i termini e le condizioni
        </label>
        {errors.accettaTermini && (
          <p className="errore">{errors.accettaTermini.message}</p>
        )}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? "Registrazione..." : "Registrati"}
      </button>
    </form>
  );
}

Valori di Default e Trasformazioni

Puoi usare defaultValues con lo schema:

const ProfiloSchema = z.object({
  bio: z.string().max(500).default(""),
  sito: z.string().url().or(z.literal("")).default(""),
  newsletter: z.boolean().default(false),
});

type ProfiloForm = z.infer<typeof ProfiloSchema>;

function ProfiloPage() {
  const { register, handleSubmit } = useForm<ProfiloForm>({
    resolver: zodResolver(ProfiloSchema),
    defaultValues: {
      bio: "",
      sito: "",
      newsletter: false,
    },
  });

  // ...
}

Validazione in Modalita’ onChange

Per validare mentre l’utente digita:

const { register, handleSubmit, formState: { errors } } = useForm<LoginForm>({
  resolver: zodResolver(LoginSchema),
  mode: "onChange", // valida ad ogni cambiamento
  // oppure: mode: "onBlur" -- valida quando il campo perde il focus
});

Errori Lato Server

Puoi combinare la validazione Zod con errori dal server:

const { setError, handleSubmit } = useForm<LoginForm>({
  resolver: zodResolver(LoginSchema),
});

const onSubmit = async (dati: LoginForm) => {
  const risposta = await fetch("/api/login", {
    method: "POST",
    body: JSON.stringify(dati),
  });

  if (!risposta.ok) {
    const errore = await risposta.json();
    // Imposta errori dal server
    setError("email", { message: errore.messaggio });
    // Oppure errore generico del form
    setError("root", { message: "Credenziali non valide" });
  }
};