Capítulo 19 de 39

Chapter 19: setError

Core Idea

setError(name, error, options?) manually attaches an error to a field (or a global root/server error), one field per call — the tool for surfacing server-side validation failures that RHF's own rules never ran.

Key Concepts

  • error shape: { type?, message?, types? }type is an identifier ("required", "custom", "serverError") exposed as errors[name].type; message is the display string; types is a Record<string, string|boolean> for attaching several simultaneous messages to one field (pair with criteriaMode: "all" if you also want built-in/schema validation to populate types the same way).
  • Overridden by passing validation: an error set on a registered field is cleared as soon as that field's own register rules re-validate and pass — setError doesn't outlast real validation for registered inputs.
  • Persists for unregistered names: an error set on a name that was never registered sticks around until you call clearErrors yourself.
  • root. for global/server errors* (7.43.0+): setError("root.serverError", { type: "400" }) — this kind of error does not persist across submissions the way a field error does.
  • shouldFocus: only works if the target field's ref is actually attached to a real DOM element (not for custom-registered virtual inputs), and never works on a disabled input.
  • Always forces isValid to false immediately: but that's a manual override, not a validation result — the next real validation pass (change, submit, trigger()) recomputes and can overwrite it.
  • Reserved field names: avoid type, root, ref, types, message, form as actual field names — they collide with the internal FieldError/GlobalError shape.
  • No batch call: setError sets exactly one error per invocation — looping over a list of server-returned field errors and calling it once per entry is the standard pattern for multiple errors.

Code Examples

setError("username", { type: "manual", message: "Dont Forget Your Username Should Be Cool!" })

setError("test", { type: "focus" }, { shouldFocus: true })

setError("lastName", { types: { required: "This is required", minLength: "This is minLength" } }) // needs criteriaMode: "all"

setError("root.serverError", { type: response.statusCode }) // global/server error, doesn't persist across submits
  • What it demonstrates: the four shapes — single message, focus-on-set, multiple simultaneous messages via types, and a global server error.

Anti-patterns

  • Expecting a manually-set error on a registered field to survive its own passing validation: it won't — register's rules re-validating and passing clears it.
  • Naming a field type, root, message, etc.: collides with FieldError/GlobalError internals.
  • Calling setError once with an array expecting it to set multiple fields: it only ever sets one; loop it.

Key Takeaways

  1. setError is for errors RHF's own validation didn't produce (server responses, custom async checks outside validate) — expect it to be overwritten by real validation on registered fields.
  2. root.* is the dedicated namespace for global/server errors that shouldn't attach to a specific field and shouldn't persist submission-to-submission.
  3. types + criteriaMode: "all" is how a single field shows more than one simultaneous validation message.

Connects To

  • useform-clearerrors: the inverse; required to remove an error set on an unregistered name.
  • useform-handlesubmit: the typical place a caught server error becomes a setError("root.serverError", ...) call.
  • useform: criteriaMode: "all" is what makes types populate from built-in/schema validation too, not just manual setError calls.