Introduzione alla Validazione dei Form in React
La gestione dei form è una delle attività più comuni e, paradossalmente, più complesse nello sviluppo di applicazioni web con React. Non si tratta solo di raccogliere dati dall'utente, ma di garantire che tali dati siano accurati, sicuri e completi prima di essere inviati a un server.
Validare un form significa implementare un sistema di controllo che verifichi se l'input dell'utente rispetta determinati criteri (es. un'email valida, una password di almeno 8 caratteri, un campo obbligatorio non vuoto). In React, a causa della natura dichiarativa della libreria, abbiamo diverse strategie per gestire questo processo: dai "controlled components" alla gestione dello stato tramite librerie esterne.
In questo articolo esploreremo l'evoluzione della validazione, partendo dall'implementazione manuale per comprendere i meccanismi interni, fino all'utilizzo di standard industriali come React Hook Form e Zod per scalare l'applicazione.
L'Approccio Manuale: Controlled Components
In React, un componente è "controllato" quando il suo valore è guidato dallo stato del componente stesso. Questo significa che ogni cambiamento nell'input aggiorna lo stato, e lo stato aggiorna l'input.
Perché usare i Controlled Components?
L'approccio manuale è ideale per form molto semplici o quando è necessaria una validazione in tempo reale estremamente specifica che non segue schemi standard. Ci permette di avere il controllo totale su ogni singolo carattere digitato.
Implementazione pratica
Vediamo come implementare un form di registrazione semplice con validazione manuale dello stato.
import React, { useState } from 'react';
const SimpleForm = () => {
const [formData, setFormData] = useState({
username: '',
email: '',
});
const [errors, setErrors] = useState({});
const validate = () => {
let tempErrors = {};
if (!formData.username) {
tempErrors.username = "Il nome utente è obbligatorio";
} else if (formData.username.length < 3) {
tempErrors.username = "Il nome deve essere di almeno 3 caratteri";
}
const emailRegex = /^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$/;
if (!formData.email) {
tempErrors.email = "L'email è obbligatoria";
} else if (!emailRegex.test(formData.email)) {
tempErrors.email = "L'email non è valida";
}
setErrors(tempErrors);
return Object.keys(tempErrors).length === 0;
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData({ ...formData, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
if (validate()) {
console.log("Dati validati con successo:", formData);
alert("Form inviato!");
} else {
console.log("Errori di validazione presenti");
}
};
return (
<form
<div>
<label>Username:</label>
<input name="username" value={formData.username} />
{errors.username && <span style={{ color: 'red' }}>{errors.username}</span>}
</div>
<div>
<label>Email:</label>
<input name="email" value={formData.email} />
{errors.email && <span style={{ color: 'red' }}>{errors.email}</span>}
</div>
<button type="submit">Invia</button>
</form>
);
};
export default SimpleForm;
Spiegazione del codice:
In questo esempio, utilizziamo un unico oggetto formData per gestire tutti i campi. La funzione validate viene eseguita al momento dell'invio. Se l'oggetto tempErrors rimane vuoto, il form è valido. Questo metodo, sebbene didattico, diventa rapidamente ingestibile quando il form cresce a 10 o 20 campi, poiché ogni modifica di stato provoca un re-render dell'intero componente.
Scalare con React Hook Form (RHF)
Quando le applicazioni crescono, l'approccio dei controlled components causa problemi di performance (re-render eccessivi) e richiede una quantità enorme di codice boilerplate per gestire ogni singolo errore.
React Hook Form rivoluziona questo approccio utilizzando i "uncontrolled components" tramite l'API ref di React. Invece di aggiornare lo stato a ogni tasto premuto, RHF legge i valori solo quando necessario, riducendo drasticamente i re-render.
Vantaggi di React Hook Form
- Performance: Meno re-render rispetto ai controlled components.
- Meno codice: Non serve creare un
useStateper ogni campo. - Integrazione: Si sposa perfettamente con librerie di validazione dello schema.
Validazione basata su Schema con Zod
Scrivere logiche di validazione con if/else all'interno dei componenti sporca il codice e rende difficile il riutilizzo della logica (ad esempio, se volessimo usare la stessa validazione sia nel frontend che nel backend Node.js).
Zod è una libreria di dichiarazione dello schema TypeScript-first che permette di definire la "forma" dei dati attesi. Se i dati non corrispondono allo schema, Zod restituisce un errore dettagliato.
Implementazione di RHF + Zod
Per collegare Zod a React Hook Form, utilizziamo un "resolver". Vediamo l'esempio completo.
# Installazione delle dipendenze
npm install react-hook-form zod @hookform/resolvers
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
// 1. Definizione dello schema di validazione
const schema = z.object({
username: z.string().min(3, { message: "Il nome deve essere di almeno 3 caratteri" }),
email: z.string().email({ message: "Inserisci un indirizzo email valido" }),
password: z.string().min(8, { message: "La password deve essere di almeno 8 caratteri" }),
confirmPassword: z.string()
}).refine((data) => data.password === data.confirmPassword, {
message: "Le password non coincidono",
path: ["confirmPassword"],
});
const AdvancedForm = () => {
const {
register,
handleSubmit,
formState: { errors }
} = useForm({
resolver: zodResolver(schema),
mode: "onBlur" // Valida quando l'utente esce dal campo
});
const => {
console.log("Dati validati e pronti per l'invio:", data);
};
return (
<form style={{ display: 'flex', flexDirection: 'column', gap: '1rem', maxWidth: '400px' }}>
<div>
<label>Username</label><br/>
<input {...register("username")} />
<p style={{ color: 'red' }}>{errors.username?.message}</p>
</div>
<div>
<label>Email</label><br/>
<input {...register("email")} />
<p style={{ color: 'red' }}>{errors.email?.message}</p>
</div>
<div>
<label>Password</label><br/>
<input type="password" {...register("password")} />
<p style={{ color: 'red' }}>{errors.password?.message}</p>
</div>
<div>
<label>Conferma Password</label><br/>
<input type="password" {...register("confirmPassword")} />
<p style={{ color: 'red' }}>{errors.confirmPassword?.message}</p>
</div>
<button type="submit">Registrati</button>
</form>
);
};
export default AdvancedForm;
Analisi tecnica:
z.object: Definiamo esattamente cosa ci aspettiamo. Zod gestisce automaticamente i tipi..refine: Questa funzione è fondamentale per le validazioni che dipendono da più campi (come il confronto tra password e conferma password).register: Questa funzione di RHF collega l'input HTML allo stato interno della libreria senza bisogno divalueeonChangemanuali.mode: "onBlur": Migliora l'esperienza utente (UX) mostrando l'errore solo dopo che l'utente ha interagito con il campo e ci è uscito, evitando di mostrare errori mentre l'utente sta ancora scrivendo.
Esempi Pratici e Casi d'Uso Reali
Caso 1: Validazione Asincrona (Verifica disponibilità Username)
In molte applicazioni, non basta che un campo sia formattato correttamente; deve essere verificato nel database. React Hook Form permette di integrare validazioni asincrone direttamente nel register o tramite Zod.
// Esempio di validazione asincrona in RHF
<input
{...register("username", {
validate: async (value) => {
const response = await fetch(`/api/check-username?u=${value}`);
const data = await response.json();
return data.available || "Questo username è già preso";
}
})}
/>
Caso 2: Form Dinamici (Field Arrays)
Immaginiamo un form dove l'utente può aggiungere più numeri di telefono o indirizzi. RHF offre l'hook useFieldArray che permette di gestire liste di input validandone ogni singolo elemento in modo efficiente.
Errori Comuni e FAQ
Perché i miei errori non spariscono immediatamente?
Questo succede spesso se non è configurato correttamente il mode di useForm. Se usi il default, la validazione avviene solo al submit. Imposta mode: "onChange" o mode: "onBlur" per un feedback più rapido.
Posso usare Zod senza React Hook Form?
Certamente. Zod è indipendente. Puoi usarlo per validare i dati che arrivano da un'API o in un semplice script TypeScript. RHF è solo il "ponte" che collega Zod all'interfaccia utente.
Validazione Client-side vs Server-side
Errore critico: Affidarsi solo alla validazione in React. Ricorda che qualsiasi codice frontend può essere bypassato (es. tramite Postman o modificando il DOM). La validazione in React serve per la User Experience (UX), ma la validazione sul server è obbligatoria per la Sicurezza.
Prossimi Passi e Approfondimenti
Ora che hai implementato una validazione robusta, ecco come puoi evolvere ulteriormente le tue competenze:
- Integrazione con UI Libraries: Prova a collegare React Hook Form a Material UI o Tailwind CSS utilizzando il componente
<Controller />di RHF per gestire componenti custom che non espongono unrefstandard. - Gestione degli Stati di Caricamento: Implementa l'uso di
isSubmittingdaformStateper disabilitare il bottone di invio ed evitare duplicati. - Internazionalizzazione (i18n): Invece di scrivere i messaggi di errore in duro nel codice, usa librerie come
react-i18nextper tradurre i messaggi di Zod in base alla lingua dell'utente. - Test Automatizzati: Impara a testare i tuoi form utilizzando React Testing Library e Jest, simulando l'inserimento di dati errati e verificando che i messaggi di errore appaiano correttamente.
La validazione dei form è un pilastro dello sviluppo web. Passare da un approccio manuale a uno basato su schemi non solo pulisce il codice, ma rende l'applicazione più manutenibile e professionale.