I am facing a type mismatch issue while using "zod" schema with useForm
of "react-hook-form".
Resolver showing error for type mismatch. I set default value and not optional but error shows it has Boolean | undefined
Either form hook or zodResolver is interpreting schema and type incorrectly
Or I might be missundertood how form and zodresolver work togather.
Demo Code:
const TestSchema = z.object({
isFeatured: z.boolean().default(false),
});
type TestType = z.infer<typeof TestSchema>;
const form = useForm<TestType>({
resolver: zodResolver(TestSchema),
mode: 'onChange',
defaultValues: {
isFeatured: false,
},
});
Error: in short it showing incompatibility between { isFeatured?: boolean | undefined; } vs { isFeatured: boolean; }
- Type 'Resolver<{ isFeatured?: boolean | undefined; }, any, { isFeatured: boolean; }>'
is not assignable to
type 'Resolver<{ isFeatured: boolean; }, any, { isFeatured: boolean; }>'.
- Types of parameters 'options' and 'options' are incompatible.
- Type 'ResolverOptions<{ isFeatured: boolean; }>'
is not assignable to
type 'ResolverOptions<{ isFeatured?: boolean | undefined; }>'.
- Type 'boolean | undefined'
is not assignable to
type 'boolean'.
- Type 'undefined' is not assignable to type 'boolean'.ts(2322)
I wonder how it is interpreting it as
{ isFeatured?: boolean | undefined; }
I tried testing it with a variable of same type
const myVar:TestType = {}
this shows error
Property 'isFeatured' is missing in type '{}' but required in type '{ isFeatured: boolean; }'.ts(2741)
and added isFeatured which removed the error.
let myVar:TestType = {
isFeatured: false,
}
this means key isFeatured in TestType is not optional or not allowed as undefined
.
Then How resolver showing error that it is type of boolean | undefined
against boolean
.
Is this bug or expected behaviour for default() in schema
This is expected behavior, not a bug. The type mismatch occur because zodResolver
uses z.input<typeof schema>
for form field types. While z.infer<typeof schema>
uses z.output<typeof schema>
When you define z.boolean().default(false)
zod creates two different types of representations.
z.input
): what users can provide before validation ---> { isFeatured?: boolean | undefined }
z.output
): What you get after validation and defaults are applied ---> { isFeatured: boolean }
The zodResolver
intentionally uses the input type because it represents form validation at the user input level, where fields with defaults are technically optional since the default will be applied during validation.
The recommended approach is to extract defaults using Zod's parsing and use full type signature for your form. Here is the doc. Thanks @Bergi for pointing this out
const TestSchema = z.object({
isFeatured: z.boolean().default(false),
});
type TestType = z.input<typeof TestSchema>, any, z.output<typeof TestSchema>; // Pass the full type signature
const form = useForm<TestType>({
resolver: zodResolver(TestSchema),
mode: 'onChange',
defaultValues: TestSchema.parse({}), // This extracts { isFeatured: false }
});
The TestSchema.parse({})
call applies all default values from your schema. Giving you:
// Result of TestSchema.parse({})
{ isFeatured: false }
This approach provides a single source of truth - your defaults are defined in the schema and automatically applied to the form.