Troubleshooting
Common symptoms, what causes them, and how to fix them.
On this page
parseForm throws that a field is missing from the FormData
Cause
A field in the schema never arrived in the submitted FormData. Usually its input was never spread, or the field is not rendered under some condition.
Fix
Spread input onto every field, and always render the fields inside an .optional() object. Radios, <select> and checkboxes, which send nothing when left alone, are exempt.
A field marked .optional() still rejects an empty value
Cause
An empty text field submits "", not nothing. .optional() only allows a missing value, so a check such as .min(1) still applies to "".
Fix
Write a schema that accepts "", such as z.union([z.literal(''), z.string().min(3)]). The .min(3) then sits inside a union whose branch cannot be picked, so the browser does not check it and it is listed in dropped.
With zod/mini, every message reads “Invalid input”
Cause
zod/mini does not load a message locale by default, to keep the bundle small. Without one, every error reads Invalid input.
Fix
Load a locale with z.config(z.locales.en()) in a module the server loads first, or write a message on each check.
A radio selection is lost after a failed submission
Cause
The enum’s input is spread onto hand-written radios. After a submission input carries the previous choice as defaultValue, which collides with each radio’s own value.
Fix
Take only name and required from input, and set defaultChecked per option from state.values. @k8ordo/ui’s Radio and RadioCard take the spread as it is.
Focus does not move when the same failure comes back
Cause
useForm tells responses apart by their content and token. A hand-built state without a fresh token makes a second identical failure look like the same response.
Fix
Build on the state that parseForm returned, or give a hand-built state a fresh token every time.
Focus does not reach an error in a hidden step
Cause
A field inside a hidden step cannot take focus.
Fix
When a new state arrives, switch to the step holding the error during render. See “Multi-step forms”.
A stringbool checkbox submits “on”
Cause
A spread value does not reach @k8ordo/ui’s Checkbox, so the box submits the browser’s default on.
Fix
Render a z.stringbool() field as a plain <input {...field.input} />.
NumberField’s range error is not worded like zod
Cause
NumberField renders type="text", so the browser does not check min and max. It reports an out-of-range value itself, in @k8ordo/ui’s wording.
Fix
To keep zod’s wording, spread the derived type="number" onto a TextField. The browser checks the range, and useForm shows zod’s message.