Server errors ​

Map API validation failures onto the same issue model as client validators. Server issues participate in form validity until cleared, and can drive field meta + feedback.

System standout: protocol helper

Prefer mapServerErrorBody from @sometic/forms/server for { errors: [{ path|field, code, message }] } and flat maps, then form.setServerErrors(...). With App Shell, bindMutationForm wires this on mutation failure.

Setting errors ​

ts
import { createIssue } from "@sometic/validation";

form.setServerErrors([
    createIssue("server", "Email is already registered", { path: "email" }),
    createIssue("taken", "Username is taken", { path: "username" }),
]);

Behavior:

  1. Issues are copied into internal serverIssues.
  2. For each issue with a path, the field meta is updated (same code replaced, then appended).
  3. If feedback error is enabled and there is at least one issue, feedback becomes kind "error" with the first message (or "Server rejected the submission.").
  4. validateForm / submit treat any remaining serverIssues as invalid even when field validators pass.

Clearing ​

ts
form.clearServerErrors(); // all
form.clearServerErrors(["email"]); // by path

When clearing by path, field issues are stripped only when issue.code === "server". Custom codes such as "taken" remain on the field until you revalidate, clearErrors, or replace them with a new setServerErrors cycle. Prefer code: "server" when you want path clears to remove them automatically.

Client external errors ​

ts
form.setErrors([createIssue("conflict", "Fix the highlighted fields", { path: "email" })]);
form.clearErrors();
form.clearErrors(["email"]);

setErrors feeds clientIssues (not tagged as server). Use for cross-field messages you compute outside validators.

Reading issues ​

ts
form.getIssues(); // fields + client + server
form.getFieldIssues("email");
form.getFieldMeta("email").issues;
form.getFeedback(); // kind + message + issues

Typical submit flow ​

ts
const submit = form.handleSubmit({
    onValid: async (values) => {
        const response = await fetch("/api/register", {
            method: "POST",
            body: JSON.stringify(values),
        });
        if (response.status === 422) {
            const body = (await response.json()) as {
                errors: Array<{ path: string; message: string }>;
            };
            form.setServerErrors(
                body.errors.map((error) =>
                    createIssue("server", error.message, { path: error.path }),
                ),
            );
            return;
        }
        form.clearServerErrors();
    },
});

Clear server errors when the user edits a path if you want optimistic recovery:

ts
form.subscribe(() => {
    /* or inside setValue wrappers */
});
// simpler: on field change
form.clearServerErrors(["email"]);

Interaction with field validation ​

After a successful client revalidation, field issues refresh from validators but retain server issues that still match an entry in serverIssues (path + code + message). Explicitly clearServerErrors when the backend state is obsolete.

Accessibility ​

Announce server failures with announceFormErrors and focus the first path via focusFirstInvalid from @sometic/forms/a11y. Keep messages actionable and tied to visible fields.