React Hook Form
useForm, register, handleSubmit, formState, Controller, useFieldArray, modurile de validare.
Actualizat
Ce este React Hook Form
React Hook Form (RHF) e cea mai folosită librărie de formulare pentru React. Un singur hook, useForm, îți dă tot ce trebuie: înregistrarea câmpurilor, validarea, erorile, starea trimiterii.
Secretul performanței: e construit pe câmpuri uncontrolled. Valorile stau în DOM, nu în state React → tastarea nu re-randează tot formularul. La 50 de câmpuri, diferența se simte.
'use client'
import { useForm } from 'react-hook-form'
type Values = { email: string; password: string }
export function LoginForm() {
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<Values>({ defaultValues: { email: '', password: '' } })
const onSubmit = async (values: Values) => {
await login(values)
}
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<input type="email" {...register('email', { required: 'Emailul e obligatoriu' })} aria-invalid={!!errors.email} />
{errors.email && <p role="alert">{errors.email.message}</p>}
<input type="password" {...register('password', { minLength: { value: 8, message: 'Minim 8 caractere' } })} />
{errors.password && <p role="alert">{errors.password.message}</p>}
<button disabled={isSubmitting}>Intră</button>
</form>
)
}Piesele useForm
| Piesă | Ce face |
|---|---|
register('name', rules) |
conectează un input: returnează name, ref, onChange, onBlur |
handleSubmit(fn) |
validează, apoi apelează fn(values) doar dacă totul e valid |
formState.errors |
erorile per câmp: errors.email?.message |
formState.isSubmitting |
true cât rulează onSubmit (async) |
formState.isDirty / dirtyFields |
ce s-a modificat față de defaultValues |
formState.isValid |
formularul e valid acum |
watch('name') |
abonare la o valoare — re-randează când se schimbă (folosește cu măsură) |
setValue, reset, setError |
control din cod: completare, resetare, eroare venită de la server |
Când se validează — modul
useForm({ mode: 'onTouched' })mode |
Validează | Experiența |
|---|---|---|
onSubmit (implicit) |
la trimitere, apoi la fiecare schimbare | erori doar după primul submit |
onBlur |
când părăsești câmpul | — |
onTouched |
după primul blur, apoi la fiecare schimbare | cel mai prietenos — nu țipă cât încă scrii |
onChange |
la fiecare tastă | agresiv, costisitor |
Componente care nu sunt input nativ: Controller
register merge cu <input>, <select>, <textarea>. Pentru componente custom (date picker, select dintr-o librărie UI) folosești Controller, care le transformă în câmpuri controlled:
<Controller
control={control}
name="country"
render={({ field, fieldState }) => <CountrySelect {...field} error={fieldState.error?.message} />}
/>Liste dinamice: useFieldArray
const { fields, append, remove } = useFieldArray({ control, name: 'phones' })
{fields.map((field, i) => (
<div key={field.id}> {/* field.id, nu indexul */}
<input {...register(`phones.${i}.number`)} />
<button type="button" onClick={() => remove(i)}>Șterge</button>
</div>
))}
<button type="button" onClick={() => append({ number: '' })}>Adaugă telefon</button>Validare cu o schemă
Regulile scrise în register se împrăștie prin JSX. Varianta recomandată: o schemă Zod + zodResolver — tipurile se deduc din schemă, iar aceeași schemă validează și pe server. Detalii în lecția despre Zod.
const form = useForm({ resolver: zodResolver(loginSchema), mode: 'onTouched' })Greșeli frecvente
watch()fără argument în componenta principală → re-randează tot formularul la fiecare tastă. FoloseșteuseWatch({ name })într-o componentă mică.key={index}înuseFieldArray— foloseștefield.id.- Validare doar în RHF, fără validare pe server.
Pe scurt
useForm→registerpe câmpuri,handleSubmit(onSubmit),formState.errors.- Uncontrolled în spate → performant;
Controllerpentru componente custom,useFieldArraypentru liste. mode: 'onTouched'+ schemă Zod cuzodResolver.