resolvers
resolvers copied to clipboard
issue: [zod] z.preprocess(): Type inferring for the input takes the type before the preprocess() method (expectation: use the type after `preprocess()`)
Version Number
5.0.1
Codesandbox/Expo snack
https://codesandbox.io/p/sandbox/vcgdy2
Steps to reproduce
Zod has a functionality called preprocess(): https://zod.dev/?id=preprocess. It allows applying transform to the input before parsing happens.
From 5.0.0, the resolver has this interface for indicating the types: useForm<FormInputValues, Context, FormOutputValues>();. It is also possible to get the typing from the schema directly, so after useForm the types can be left out.
However, FormInputValues takes the type before applying preprocess(), i.e. unknown.
To reproduce:
// schema.ts
export const preprocessArgument = (argument: string | string[] | undefined): string[] | undefined => {
if (argument === undefined || Array.isArray(argument)) {
return argument;
}
return [argument];
};
export const mySchema = z.object({
name: z.preprocess((arg) => preprocessArgument(arg as PreprocessArgument), z.string().array().optional())
});
// MyComponent.tsx
const {
control,
handleSubmit,
getValues
} = useForm({
resolver: zodResolver(mySchema),
defaultValues
});
Now if I inspect the type of resolver, I get:
(property) resolver?: Resolver<{
name?: unknown;
}, unknown, {
name?: string[] | undefined;
}> | undefined
Expected behaviour
I expect the resolver picks up the type after the preprocess() method.
What browsers are you seeing the problem on?
Chrome
Relevant log output
Code of Conduct
- [x] I agree to follow this project's Code of Conduct
that explains why i needed to this, right?
const optionalPositiveNumber = z.preprocess(
(value) => (value === '' ? undefined : Number(value)),
z.number({ message: 'Must be numeric' }).min(1, 'Must be positive').nullish(),
) as unknown as z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
The current behavior is sound and intentional. Preprocess can accept anything, hence the input type is unknown. The defaultValues param lets you specify pre-transform defaults, so the value needs to conform to the schema's input type. Everything here is behaving as expected.
That said, the latest versions of Zod now auto-detect a manual input type signature in the preprocess function, so your example will work as is. Just upgrade to latest and give it another shot. But keep in mind that you're going around Zod here, and Zod is not able to guarantee that the argument to preprocessArgument is actually going to be string | string[] | undefined.
A more sound approach would be something like this:
export const preprocessArgument = (argument: string | string[] | undefined): string[] | undefined => {
if (argument === undefined || Array.isArray(argument)) {
return argument;
}
return [argument];
};
export const mySchema = z.object({
name: z
.union([z.string(), z.string().array()])
.optional()
.transform(preprocessArgument)
.pipe(z.string().array().optional()),
});
But if you can trust (based on the structure of your form) that the name field will always be string | string[] | undefined then this is probably overkill.