reactjstypescriptreact-hook-formzod

Zod + react-hook-form: .default(false) still resolves as boolean | undefined as if it were.optional()


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


Solution

  • 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.

    1. Input type (z.input): what users can provide before validation ---> { isFeatured?: boolean | undefined }
    2. Output type (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.