Fetch clients validate parameters and payload before making any network request - using the exact same schemas as the server. Invalid data throws immediately, no round trip needed.
π Validation Schemas β
Beyond automatic fetch validation, each client exposes validationSchemas for use directly in your UI - ideal for real-time form feedback:
const { validationSchemas } = fetchClients["users"];
validationSchemas.params; // parameter validation
validationSchemas.json.POST; // JSON payload validation for POSTEach schema has four methods:
check(data)- fast boolean check, safe to call on every keystrokeerrors(data)- returnsArray<ValidationErrorEntry>with field-level detail; only call aftercheckreturns falseerrorMessage(data)- all errors as a single readable stringerrorSummary(data)- brief overview, e.g."2 validation errors found across 2 fields"
check is cheap. errors, errorMessage, and errorSummary are heavier - gate them behind check.
πͺ Field Paths β
Nested field errors use arrow notation: "customer β address β city". Match them with word-boundary regex to avoid false positives:
const emailError = errors.find(({ path }) => /\bemail\b/.test(path));β‘ Per-Field Validation Performance β
Schemas validate entire objects, not individual fields. This creates a subtle issue when validating fields as users type: on a partially-filled form, check returns false for missing required fields - not just the one you're testing - which triggers unnecessary errors() calls on every keystroke.
The fix is to merge the actual field value into a fully-valid placeholder payload, so check only fails when the field under test actually has a problem:
// Define a valid baseline - all required fields filled with values that pass all constraints.
// This is a one-time setup per form, not per keystroke.
const validPayload = { name: "Valid Name", email: "valid@example.com", age: 25 };
// On input event for "name" - override just that field
const payload = { ...validPayload, name: event.target.value };
if (!validationSchemas.json.POST.check(payload)) {
const nameError = validationSchemas.json.POST.errors(payload).find(e => e.path === "name");
// show nameError.message near the name field
}Each field gets its own merge - { ...validPayload, email: event.target.value } for email, and so on. The placeholder values for other fields are never submitted anywhere, they just keep check from firing false negatives.
Most forms don't need this. If you validate on submit rather than on input, or your form has only a few fields, direct validation works fine. It matters for complex forms with many required fields that validate in real time.
On submit, always validate the actual payload - not the merged one:
if (!validationSchemas.json.POST.check(actualPayload)) {
const errors = validationSchemas.json.POST.errors(actualPayload);
// surface all errors at once
return;
}
await useFetch.POST([], actualPayload);