v5.0: Schema with .default fields result in inaccurate typing
Describe the bug With the new v5.0.0, I got a type error:
const FormSchema = z.object({
debug_mode: z.boolean().default(true),
});
type FormValues = z.infer<typeof FormSchema>;
const form = useForm<FormValues>({
resolver: zodResolver(FormSchema),
defaultValues: {
debug_mode: true,
},
});
The automatic resolution gives me:
const form: UseFormReturn<{
debug_mode?: boolean | undefined;
}
I think this is incorrect as the schema does not have any optional members.
When I remove the .default() in the schema, so it looks like this:
const FormSchema = z.object({
debug_mode: z.boolean(),
});
the resolution changes to:
const form: UseFormReturn<{
debug_mode: boolean;
}
which is consistent with type FormValues = z.infer<typeof FormSchema>; again.
Codesandbox link (Required)
As this is a typing issue and Code Sandbox uses JS, it's not really applicable.
Hey! What's the type error you're seeing? Also, any chance you could throw up a quick CodeSandbox so we can take a look?
Type 'Resolver<{ debug_mode?: boolean | undefined; }, any, { debug_mode: boolean; }>' is not assignable to type 'Resolver<{ debug_mode: boolean; }, any, { debug_mode: boolean; }>'.
Types of parameters 'options' and 'options' are incompatible.
Type 'ResolverOptions<{ debug_mode: boolean; }>' is not assignable to type 'ResolverOptions<{ debug_mode?: boolean | undefined; }>' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Type 'boolean | undefined' is not assignable to type 'boolean'.
Type 'undefined' is not assignable to type 'boolean'.ts(2322)
Without exactOptionalPropertyTypes it changes to:
Type 'Resolver<{ debug_mode?: boolean | undefined; }, any, { debug_mode: boolean; }>' is not assignable to type 'Resolver<{ debug_mode: boolean; }, any, { debug_mode: boolean; }>'.
Types of parameters 'options' and 'options' are incompatible.
Type 'ResolverOptions<{ debug_mode: boolean; }>' is not assignable to type 'ResolverOptions<{ debug_mode?: boolean | undefined; }>'.
Type 'boolean | undefined' is not assignable to type 'boolean'.
Type 'undefined' is not assignable to type 'boolean'.ts(2322)
Unfortunately I couldn't get the code sandbox to properly work with TS instead of JS, but it should be reproducible fairly easily with the snippets above.
Same here: https://github.com/react-hook-form/resolvers/issues/759
Just wondering, why are you manually typing useForm<FormValues>? V5's supposed to handle that automatically. If you do need to type it, here is the way to go.
type FormValues = z.input<typeof FormSchema>; // Notice the z.input
z.infer and z.output are the same. I'll clarify this in the release notes.
The TypeScript syntax for useForm is: useForm<Input, Context, Output>.
The manual typing was a leftover of the previous version. Like @jonathanwilke, I did not expect a breaking change that was not mentioned in the release notes.
But:
Even removing the manual typing, when .default() is used in the schema definition, this leads to errors further down the line because this:
const FormSchema = z.object({
debug_mode: z.boolean().default(true),
});
in automatic mode still resolves to this:
const form: UseFormReturn<{
debug_mode?: boolean | undefined;
}
This leads to problems later on:
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
/>
</FormControl>
Now checked can be true, false or undefined, which is not accepted by the component I use, leading to a typing error there:
Type '{ checked: boolean | undefined; onCheckedChange: (...event: any[]) => void; }' is not assignable to type 'Omit<SwitchProps & RefAttributes<HTMLButtonElement>, "ref">' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties.
Types of property 'checked' are incompatible.
Type 'boolean | undefined' is not assignable to type 'boolean'.
Type 'undefined' is not assignable to type 'boolean'.ts(2375)
Without .default() it resolves to boolean only. I think adding a default value in the schema should not make the result undefined.
Hmm, I understand what you're saying. Resolvers are simply functions that connect react-hook-form with validation libraries.
When you use zod's .default() and define a schema like this:
const schema = z.object({
debug_mode: z.boolean().default(true),
});
type Schema = z.input<typeof schema>; // { debug_mode?: boolean | undefined; }
The Schema type becomes { debug_mode?: boolean | undefined; }. That's why the resolver returns this type.
Based on Zod's tests, this behavior appears to be intentional.
The test makes sense when I think about it. If I put something in when a default is defined, I can use an input of undefined and it will still work, because it has the default.
The output however will never contain something undefined (because the default is there as fallback).
So the resulting type should maybe match the output and not the input? z.infer instead of z.input?
I think here is the type contradiction, cc @kotarella1110 @jorisre
const FormSchema = z.object({
debug_mode: z.boolean().default(true), // 🔎 which turn this field to be optional debug_mode?: boolean | undefined;
});
type FormValues = z.infer<typeof FormSchema>; // 🔎 infer type which debug_mode: boolean
const form = useForm<FormValues>({
resolver: zodResolver(FormSchema),
// ✅ in this case, the infer contradicts with <FormValues>
defaultValues: {
debug_mode: true,
},
});
I am having issue with this as well. Its making it so that I cant pass the result from calling useFieldArray as a prop in a good way.
The issue resolves by removing the .default([])
I'm also having this issue
fix waiting room
I had to explicit use input + output to make it work without errors:
type FormValues = z.infer<typeof schema>; // same as z.output
const form = useForm<z.input<typeof schema>, any, FormValues>({
resolver: zodResolver(schema),
defaultValues: {
...
I think it's more than just a typing issue. And I think it's more than just an issue with the resolver.
I'm currently using:
-
"react-hook-form": "^7.53.1" -
"@hookform/resolvers": "^3.9.1"
When I upgrade just react-hook-form (not the resolvers) ^7.55.0... I'm failing validations on all boolean fields that have a default value set to false.
I'm currently using "zod": "^3.24.2"
Reverting back to "react-hook-form": "^7.53.1" (I haven't tried other versions) relieves the problem.
I'm having this issue as well.
I ended up removing .default for enums and booleans. However, it would be great to have default resolve/infer the type of the default value.
I was confused for a while too, but I believe the behavior in the latest versions of the zod resolver and react-hook-form are correct. It seems like the resolver is inferring the input and output types correctly, and typing defaultValues using the input types (as I would expect). By specifying a default for a boolean field, you're saying that the form input doesn't need to specify a value for that boolean, and in one way or another your form will apply a default value so that it will always be present in the output. Here's a codesandbox demonstrating an optional boolean field: https://codesandbox.io/p/devbox/relaxed-sinoussi-cv2kd3?workspaceId=ws_Sudas5QnHp5g6vgiCpE2ic
so what's the solution here?
I'm wondering how the new zod codecs might change things
@abielzulio I think the solution is not to specify a generic FormValues on useForm, and you probably shouldn't use .default except in rare situations (like maybe a radio button that cannot be unset, or a toggle, or a dropdown that always has a value).
@IanVS isn't the solution just to change z.infer (aka z.output) to z.input? I think .default is fine to use, no?
And can this issue be closed now?
Personally I'd rather let typescript infer the types itself rather than specifing overrides, but yes I think that using z.input would likely work. The full signature is useForm<TFieldValues extends FieldValues = FieldValues, TContext = any, TTransformedValues = TFieldValues>, but again, when specifying a resolver, you don't really need to provide any of those.
im having the same issue here when doing my formschema
const formSchema = z.object({
isFree: z.boolean().default(false),
});
const form = useForm<z.input<typeof formSchema>, any, z.infer<typeof formSchema> >({
resolver: zodResolver(formSchema),
defaultValues: {
isFree: Boolean(initialData.isFree)
}, //initial title
});
i think the best way is to explicitly say:
useForm<z.input<typeof formSchema>, any, z.infer<typeof formSchema>>
or remove the default(false) in formSchema.
Question is is it good enough to have the defaultValues in zodResolver?