T
TanStack15mo ago
fair-rose

Displaying Validation Messages with Zod in Next.js

I’m having trouble showing validation messages with Zod as the validator in my Next.js project. Here’s my code:
const form = useForm({
defaultValues: {
email: "",
firstName: "",
lastName: "",
password: "",
},
onSubmit: async ({ value }) => {
await signUp(value);
},
validatorAdapter: zodValidator(),
validators: {
onChange: insertUserSchema,
},
});
const form = useForm({
defaultValues: {
email: "",
firstName: "",
lastName: "",
password: "",
},
onSubmit: async ({ value }) => {
await signUp(value);
},
validatorAdapter: zodValidator(),
validators: {
onChange: insertUserSchema,
},
});
<form.Field
children={(field) => <InputGroup field={field} type="password" />}
name="password"
/>
<form.Field
children={(field) => <InputGroup field={field} type="password" />}
name="password"
/>
function InputGroup({ field, ...props }: InputGroupProps) {
return (
<div className="flex flex-col gap-3">
<label className="capitalize" htmlFor={field.name}>
{field.name}
</label>
<Input
name={field.name}
onChange={(event) => field.handleChange(event.target.value)}
{...props}
/>
{field.state.meta.errors ? (
<em role="alert">{field.state.meta.errors.join(", ")}</em>
) : null}
</div>
);
}
function InputGroup({ field, ...props }: InputGroupProps) {
return (
<div className="flex flex-col gap-3">
<label className="capitalize" htmlFor={field.name}>
{field.name}
</label>
<Input
name={field.name}
onChange={(event) => field.handleChange(event.target.value)}
{...props}
/>
{field.state.meta.errors ? (
<em role="alert">{field.state.meta.errors.join(", ")}</em>
) : null}
</div>
);
}
I’d appreciate any help. Let me know if you need more details!
30 Replies
conscious-sapphire
conscious-sapphire15mo ago
Check form.errorMap
fair-rose
fair-roseOP14mo ago
i found the errors inside of field.options.form.state.errors but field.state.meta.errors is empty
conscious-sapphire
conscious-sapphire14mo ago
Please open a bug report with a minimum reproduction
fair-rose
fair-roseOP14mo ago
GitHub
Unable to display Validation Messages with Zod in Next.js · Issue #...
Describe the bug I am encountering an issue in my Next.js project where I am unable to display validation messages. Although the field.form.state.errors property correctly contains the errors, the ...
fair-rose
fair-roseOP14mo ago
Adding the following code works:
validators={{
onChange: userSchema.shape.email,
}}
validators={{
onChange: userSchema.shape.email,
}}
in
<form.Field
name="email"
children={(field) => {
console.log(field.state.meta.errors);
console.log(field.form.state.errors);

return (
<>
<label htmlFor={field.name}>{field.name}</label>
<input
onChange={(event) => field.handleChange(event.target.value)}
type="email"
/>
<>
{field.state.meta.isTouched && field.state.meta.errors.length ? (
<em>{field.state.meta.errors.join(",")}</em>
) : null}
{field.state.meta.isValidating ? "Validating..." : null}
</>
</>
);
}}
validators={{
onChange: userSchema.shape.email,
}}
/>;
<form.Field
name="email"
children={(field) => {
console.log(field.state.meta.errors);
console.log(field.form.state.errors);

return (
<>
<label htmlFor={field.name}>{field.name}</label>
<input
onChange={(event) => field.handleChange(event.target.value)}
type="email"
/>
<>
{field.state.meta.isTouched && field.state.meta.errors.length ? (
<em>{field.state.meta.errors.join(",")}</em>
) : null}
{field.state.meta.isValidating ? "Validating..." : null}
</>
</>
);
}}
validators={{
onChange: userSchema.shape.email,
}}
/>;
However, I’m not sure if this is expected.
conscious-sapphire
conscious-sapphire14mo ago
Well that works And is expected, but I THINK your OP code should be fine too
fair-rose
fair-roseOP14mo ago
when i follow the example on https://tanstack.com/form/latest/docs/framework/react/examples/zod field.state.meta.errors is also empty via console.log but the message gets displayed 😄
React TanStack Form Zod Example | TanStack Form Docs
An example showing how to implement Zod in React using TanStack Form.
conscious-sapphire
conscious-sapphire14mo ago
Wat
fair-rose
fair-roseOP14mo ago
my fault, a tab reload resolved it..
conscious-sapphire
conscious-sapphire14mo ago
Oh haha Sall good it happens 🙂
sunny-green
sunny-green14mo ago
So I dealt with something similar that is causing me to wait to adopt tanstack form. I tried to migrate all of my forms but they all operate with a zod schema at the form level rather than at the field level. I thought I saw a Github issue where you mentioned being able to set individual field level errors from a general for validator but I can't find it. Is it expected long term to be able to just set a single validations schema at the form level and still get individual field level errors?
conscious-sapphire
conscious-sapphire14mo ago
You can already do that Always have been, ever since validators were introduced
sunny-green
sunny-green14mo ago
Maybe I misunderstanding but when I added a zod validator, I only got a single error message for the whole form, not on a per field basis. Are you saying that is a bug? I'm happy to pull up an example
conscious-sapphire
conscious-sapphire14mo ago
No, that's the intended behavior. But it should match the shape of the schema IIRC So you should be able to: - form.state.meta.errorMap?.onChange?.yourField To get the error (it's possible it doesn't match the shape and I'm misremembering. If that's the case that is a bug we need to fix. I'll for sure double check before we v1)
sunny-green
sunny-green14mo ago
Thank you, that makes sense. I'm checking now to see if that works I didn't see quite how to do that in the docs but I think the docs were down temporarily when I checked
conscious-sapphire
conscious-sapphire14mo ago
Well and I don't think we mention how to do that in the docs
sunny-green
sunny-green14mo ago
This is honestly why I joined the Discord 😂
conscious-sapphire
conscious-sapphire14mo ago
We're actually missing a pretty mission critical page that's almost done (how to move TanStack Form into your own library) And I wanna mention how to do that in there Always open for feedback on how to improve the docs. They're our biggest work needed prior to 1.x
sunny-green
sunny-green14mo ago
Yea this is what I'm seeing in the form state for this form setup when I don't fill out a password
const form = useForm<z.infer<typeof schema>, ReturnType<typeof zodValidator>>({
validatorAdapter: zodValidator(),
validators: { onChange: schema },
async onSubmit({ value }) {
setErrorMessage(null);
try {
await mutation.mutateAsync(value);
} catch (err) {
if (isAxiosError(err)) {
setErrorMessage(err.response?.data?.message);
return;
}
throw err;
}
}
});
const form = useForm<z.infer<typeof schema>, ReturnType<typeof zodValidator>>({
validatorAdapter: zodValidator(),
validators: { onChange: schema },
async onSubmit({ value }) {
setErrorMessage(null);
try {
await mutation.mutateAsync(value);
} catch (err) {
if (isAxiosError(err)) {
setErrorMessage(err.response?.data?.message);
return;
}
throw err;
}
}
});
And the schema
const schema = z.object({
email: z.string().email({ message: 'Must be a valid email' }),
password: z.string().min(1, 'Required')
});
const schema = z.object({
email: z.string().email({ message: 'Must be a valid email' }),
password: z.string().min(1, 'Required')
});
I didn't see a way to line up the required message with the password field itself
No description
conscious-sapphire
conscious-sapphire14mo ago
Ugh, Zod, rly?
sunny-green
sunny-green14mo ago
Sorry if I hijacked this thread. I wasn't quite sure if it was related or not
conscious-sapphire
conscious-sapphire14mo ago
Nah, you're good Yeah, seems like I need to do some work to make this work the way I would expect
sunny-green
sunny-green14mo ago
If I can help in any way, just let me know. My migration is all on a branch for now and my plan was just to wait for V1 and then try to help in the meantime.
conscious-sapphire
conscious-sapphire14mo ago
Can you open an issue with this request in it actually?
sunny-green
sunny-green14mo ago
Sure thing
conscious-sapphire
conscious-sapphire14mo ago
For form.errorMap.onChange, EG, to match the shape of the schema?
conscious-sapphire
conscious-sapphire14mo ago
GitHub
resolvers/zod/src/zod.ts at master · react-hook-form/resolvers
📋 Validation resolvers: Yup, Zod, Superstruct, Joi, Vest, Class Validator, io-ts, Nope, computed-types, typanion, Ajv, TypeBox, ArkType, Valibot, effect-ts and VineJS - react-hook-form/resolvers
sunny-green
sunny-green14mo ago
Yea I believe that works for RHF. I actually had built my own because I really don't like RHF though
export const joinLookupArrayPath = (path: (string | number)[]): string => {
return path.reduce<string>((joined, part) => {
if (isNumber(part)) return joined + `[${part}]`;
if (!part) return joined;
if (!joined) return part;
return joined + '.' + part;
}, '');
};


export const zodValidate = <S extends z.ZodTypeAny>(schema: S, async = false) => {
async function validate(currentVals: DeepPartial<z.TypeOf<S>>) {
const parsed = async ? await schema.safeParseAsync(currentVals) : schema.safeParse(currentVals);
if (parsed.success) return;
return parsed.error.issues.reduce<Record<string, string>>((errors, issue) => {
const path = joinLookupArrayPath(issue.path);
// Only report on the first message in the form, for now
// Later we will try to work only with arrays and have each form field handle
// multiple errors at once
if (!errors[path]) errors[path] = issue.message;
return errors;
}, {});
}
return validate;
};
export const joinLookupArrayPath = (path: (string | number)[]): string => {
return path.reduce<string>((joined, part) => {
if (isNumber(part)) return joined + `[${part}]`;
if (!part) return joined;
if (!joined) return part;
return joined + '.' + part;
}, '');
};


export const zodValidate = <S extends z.ZodTypeAny>(schema: S, async = false) => {
async function validate(currentVals: DeepPartial<z.TypeOf<S>>) {
const parsed = async ? await schema.safeParseAsync(currentVals) : schema.safeParse(currentVals);
if (parsed.success) return;
return parsed.error.issues.reduce<Record<string, string>>((errors, issue) => {
const path = joinLookupArrayPath(issue.path);
// Only report on the first message in the form, for now
// Later we will try to work only with arrays and have each form field handle
// multiple errors at once
if (!errors[path]) errors[path] = issue.message;
return errors;
}, {});
}
return validate;
};
But yes, I was modeling it off of RHF
conscious-sapphire
conscious-sapphire14mo ago
👀 Instead of a feature request you wanna just... Make a PR?
sunny-green
sunny-green13mo ago
Just filed the issue. I could try this weekend if that helps but I'm not sure how much time I can commit. I would just need to know how you want it to actually work. @crutchcorn I should be more available this week and could try to knock this out soon. Do you have any suggestions on where to get started? I think I could just focus on the zod adapter and use some of the newer features that allow field level errors to be set at the form level

Did you find this page helpful?