Capítulo 2 de 39
This page is a recipe collection for problems that come up once a form grows past the basics: accessibility, multi-step wizards, composing reusable input components, deep component trees, performance, testing, and integrating with Next.js Server Actions / useActionState.
aria-invalid attribute with a role="alert" error span is what makes RHF's validation errors actually announced by screen readers.useForm() return value on React Context so deeply nested inputs can call register/control without prop drilling.useFormContext with a render-prop child, used to connect one input deep in a tree without re-plumbing context everywhere.Controller's field.onChange/value to convert between the DOM's string values and a richer type (number, Date, etc.), while still spreading the rest of field so ref/onBlur/name survive.handleSubmit, then hand the already-parsed data to the Server Action's dispatch function — no useEffect bridge needed.// Accessible error pattern
<input
id="name"
aria-invalid={errors.name ? "true" : "false"}
{...register("name", { required: true, maxLength: 30 })}
/>
{errors.name?.type === "required" && <span role="alert">This is required</span>}
aria-invalid, role="alert") that turn a plain validation error into one a screen reader announces.// Server Action + useActionState, no useEffect bridge
const [state, submitAction, isPending] = useActionState(updateProfile, null)
const { register, handleSubmit, formState: { errors } } = useForm()
<form onSubmit={handleSubmit((data) => submitAction(data))}>
isPending/state come straight from useActionState with nothing to synchronize manually. Field-level server errors get mapped back with setError inside a useEffect that watches state — a legitimate use of the effect since it syncs external state in.render() in act() during tests: RHF's internal async validation already causes a post-test-return render; wrapping in act() unnecessarily hides real problems — use find* queries instead and await them.onChange/value on a Controller field without spreading the rest of field: drops ref (breaks focus-on-error) and onBlur (breaks isTouched).FormProvider/useFormContext per row, or use Controller with an explicit defaultValue sourced from getValues(), to survive mount/unmount cycles.aria-invalid and role="alert" have to be added by hand.FormProvider/useFormContext is the standard way to avoid prop-drilling register/control through deep trees, at the cost of the whole subtree re-rendering on form updates (mitigate with memo + a custom prop comparator).getValues(), don't rely on component state surviving.useActionState, RHF validation runs first inside handleSubmit; the Server Action only ever receives already-valid data.<Form> component's action prop is the lower-level alternative when you don't need useActionState's pending/result state.