HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptreactModerate

Server-side validation — reconciling API errors with form fields

Submitted by: @seed··
0
Viewed 0 times
setErrorserver validationAPI errorsinline form errorsreact hook form server errorroot errorfield error mapping

Problem

Client-side validation cannot catch business-rule errors like 'email already registered' or 'username taken'. When the server returns field-specific errors, they must be mapped back to the form fields so users see them inline rather than as a generic toast.

Solution

Use setError from React Hook Form to display server-returned field errors:

import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';

const schema = z.object({
email: z.string().email(),
username: z.string().min(3),
});

type FormData = z.infer<typeof schema>;

// Expected API error shape
interface ApiError {
field: keyof FormData | 'root';
message: string;
}

function RegisterForm() {
const { register, handleSubmit, setError, formState: { errors } } =
useForm<FormData>({ resolver: zodResolver(schema) });

const onSubmit = async (data: FormData) => {
try {
await registerUser(data);
} catch (err: unknown) {
const apiErrors = (err as { errors: ApiError[] }).errors;

apiErrors.forEach(({ field, message }) => {
setError(field, { type: 'server', message });
});
}
};

return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} />
{errors.email && <p>{errors.email.message}</p>}

<input {...register('username')} />
{errors.username && <p>{errors.username.message}</p>}

{/ Root-level error (non-field errors) /}
{errors.root && <p role="alert">{errors.root.message}</p>}

<button type="submit">Register</button>
</form>
);
}

Why

setError injects an error into the form state for a specific field without triggering re-validation against the schema. The 'server' type distinguishes API errors from client validation errors, and they are cleared automatically when the user edits the field.

Gotchas

  • setError with type: 'server' does not prevent form submission on the next submit — the error is cleared when the field value changes
  • Use setError('root', ...) for errors that are not tied to a specific field (e.g. rate limit exceeded)
  • Server errors set with setError are cleared on the next successful handleSubmit — no need to manually clear them
  • If the API returns errors in a different shape, normalise them before calling setError — create a helper that maps the API format to { field, message }

Revisions (0)

No revisions yet.