modular-forms
modular-forms copied to clipboard
Support for Zod 4
I would love to see a version that's compatible with Zod 4. It's currently the only thing preventing me from upgrading Zod in my project, and I'd very much like to upgrade Zod to take advantage of the dramatic performance and bundle-size improvements.
If I try to update to Zod 4, every single call to zodForm raises TypeScript errors such as:
error TS2345: Argument of type 'ZodObject<{ email: ZodString; password: ZodString; }, $strip>' is not assignable to parameter of type 'ZodType<any, any, FieldValues>'.
Types of property 'def' are incompatible.
Type '$ZodObjectDef<{ email: ZodString; password: ZodString; }>' is not assignable to type 'FieldValues | FieldValue | (FieldValues | FieldValue)[]'.
Type '$ZodObjectDef<{ email: ZodString; password: ZodString; }>' is not assignable to type 'FieldValues'.
Index signature for type 'string' is missing in type '$ZodObjectDef<{ email: ZodString; password: ZodString; }>'.
93 validate: zodForm(LoginVerificationSchema),
Hey, the zodForm function is a really simple function. Here is the code. I recommend copy it to your project and upgrade it as I am more focused on Formisch right now.
@fabian-hiller Thanks - if it helps at all, this version of the function appears to type-check and work with both Zod 3.x and Zod 4.x:
import type {
FieldValues,
FormErrors,
PartialValues,
ValidateForm,
} from '@modular-forms/solid';
import type { ZodType } from 'zod';
/**
* Creates a validation functions that parses the Zod schema of a form.
*
* @param schema A Zod schema.
*
* @returns A validation function.
*/
export function zodForm<TFieldValues extends FieldValues, TZod extends ZodType>(
schema: TZod,
): ValidateForm<TFieldValues> {
return async (values: PartialValues<TFieldValues>) => {
const result = await schema.safeParseAsync(values);
const formErrors: Record<string, string> = {};
if (!result.success) {
for (const issue of result.error.issues) {
const path = issue.path.join('.');
if (!formErrors[path]) {
formErrors[path] = issue.message;
}
}
}
return formErrors as FormErrors<TFieldValues>;
};
}
Thank you for sharing!
Although, looking at it again, while it works just fine, what it does not do is enforce that TZod has anything to do with TFieldValues like the previous version did.
This version solves that, but I suspect it doesn't work with Zod 3.
export function zodForm<
TFieldValues extends FieldValues,
TZod extends ZodType<TFieldValues>,
>(schema: TZod): ValidateForm<TFieldValues> {
return async (values: PartialValues<TFieldValues>) => {
const result = await schema.safeParseAsync(values);
const formErrors: Record<string, string> = {};
if (!result.success) {
for (const issue of result.error.issues) {
const path = issue.path.join('.');
if (!formErrors[path]) {
formErrors[path] = issue.message;
}
}
}
return formErrors as FormErrors<TFieldValues>;
};
}