resolvers icon indicating copy to clipboard operation
resolvers copied to clipboard

issue: Conditional/dynamic schema resolution no longer working

Open djshubs opened this issue 5 months ago • 3 comments

Version Number

5.1.1

Codesandbox/Expo snack

https://codesandbox.io/p/sandbox/angry-butterfly-ljt6z2

Steps to reproduce

Please see code sandbox. I get the error when when I conditionally set the formResolver

Expected behaviour

Previously, I was able to conditionally pass the schema to denote if it was update or create based on others props (please see code below and in code sandbox). However, since upgrading to 5.1.1 from 5.0.1, zodResolver gives me type errors.

export function AgencyForm({ agency }: { agency?: UpdateAgencyType }) {
  const defaultValues = agency
    ? { ...agency, legalName: agency.legalName ?? "" }
    : {
        slug: "",
        name: "",
        legalName: "",
      };
  const formResolver = agency ? updateAgencySchema : createAgencySchema;
  const form = useForm<z.infer<typeof formResolver>>({
    mode: "onBlur",
    reValidateMode: "onBlur",
    resolver: zodResolver(
      formResolver as z.ZodType<z.infer<typeof formResolver>>
    ),
    defaultValues,
  });

Here's the error I"m getting:

No overload matches this call.
  Overload 1 of 4, '(schema: Zod3Type<{ slug: string; name: string; acronym: string; legalName: string | null; } | { cuid: string; slug?: string | undefined; name?: string | undefined; acronym?: string | undefined; legalName?: string | ... 1 more ... | undefined; }, { ...; } | { ...; }>, schemaOptions?: ParseParams | undefined, resolverOptions?: NonRawResolverOptions | undefined): Resolver<...>', gave the following error.
    Argument of type 'ZodType<{ slug: string; name: string; acronym: string; legalName: string | null; } | { cuid: string; slug?: string | undefined; name?: string | undefined; acronym?: string | undefined; legalName?: string | ... 1 more ... | undefined; }, ZodTypeDef, { ...; } | { ...; }>' is not assignable to parameter of type 'Zod3Type<{ slug: string; name: string; acronym: string; legalName: string | null; } | { cuid: string; slug?: string | undefined; name?: string | undefined; acronym?: string | undefined; legalName?: string | ... 1 more ... | undefined; }, { ...; } | { ...; }>'.
      Types of property '_def' are incompatible.
        Property 'typeName' is missing in type 'ZodTypeDef' but required in type '{ typeName: string; }'.
  Overload 2 of 4, '(schema: $ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>, schemaOptions?: ParseContext<$ZodIssue> | undefined, resolverOptions?: NonRawResolverOptions | undefined): Resolver<...>', gave the following error.
    Argument of type 'ZodType<{ slug: string; name: string; acronym: string; legalName: string | null; } | { cuid: string; slug?: string | undefined; name?: string | undefined; acronym?: string | undefined; legalName?: string | ... 1 more ... | undefined; }, ZodTypeDef, { ...; } | { ...; }>' is not assignable to parameter of type '$ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>'.
      Property '_zod' is missing in type 'ZodType<{ slug: string; name: string; acronym: string; legalName: string | null; } | { cuid: string; slug?: string | undefined; name?: string | undefined; acronym?: string | undefined; legalName?: string | ... 1 more ... | undefined; }, ZodTypeDef, { ...; } | { ...; }>' but required in type '$ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>'.

What browsers are you seeing the problem on?

No response

Relevant log output


Code of Conduct

  • [x] I agree to follow this project's Code of Conduct

djshubs avatar Jun 09 '25 16:06 djshubs

I've looked into this a bit. My findings:

  1. The error in your codesanbox replicates with 5.0.1
  2. The code you pasted into your issue above doesn't error with either version

fyi It's a little confusing that the code is different between these and makes things harder to replicate/address

You're right that useForm doesn't seem to like being handed a union of Zod schema types. I'm looking for a better solution to this. But your approach of casting the schema with as z.ZodType<z.infer<typeof formResolver>> is a good workaround, and it's working for me with the latest versions of hookform. Here's a standalone code sample that doesn't throw any errors:

import { useForm } from "react-hook-form";
import "./styles.css";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";

let renderCount = 0;

const createSchema = z.object({
  slug: z.string().min(2, "Enter a valid slug"),
});

const updateSchema = createSchema
  .extend({
    cuid: z.string(),
  })
  .partial()
  .required({
    cuid: true,
  });

const isUpdate = true;

export default function App() {
  const schema = isUpdate ?  createSchema: updateSchema;
  type schema = z.input<typeof schema>;

  type FormValues = z.infer<typeof schema>;
  const { register, handleSubmit } = useForm({
    resolver: zodResolver(schema as z.ZodType<z.infer<typeof schema>>),
  });
  const onSubmit = (data: FormValues) => console.log(data);
  renderCount++;

  return (
    <div>
   
      <form onSubmit={handleSubmit(onSubmit)}>
        <input {...register("cuid")} placeholder="Cuid" />
        <input {...register("slug")} placeholder="Slug" />

        <input type="submit" />
      </form>
    </div>
  );
}

colinhacks avatar Jun 09 '25 20:06 colinhacks

Having the same issue when upgrading from 5.0.1 to 5.1.1. My use case is for doing i18n in zod schema messages.

import{ type TFunction } from 'i18next';
import { zodResolver } from '@hookform/resolvers/zod';
import { useEffect, useMemo } from 'react';
import { useForm, type FieldValues, type UseFormProps, type UseFormReturn } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import type { ZodType, ZodTypeDef } from 'zod';

type I18nNamespaces = 'namespace1' | 'namespace2';
type TFunction = TFunction<I18nNamespaces, undefined>;

type SchemaBuilder<TFormInput, TFormOutput> = (t: TFunction) => ZodType<TFormOutput, ZodTypeDef, TFormInput>;

// Replicating react-hook-form useForm signature generics (including order)
export function useI18nForm<
  TFormInput extends FieldValues,
  TFormContext = unknown,
  TFormOutput extends FieldValues = TFormInput
>(
  schemaBuilder: SchemaBuilder<TFormInput, TFormOutput>,
  formProps: Omit<UseFormProps<TFormInput, TFormContext, TFormOutput>, 'resolver'>,
  t: TFunction
): UseFormReturn<TFormInput, TFormContext, TFormOutput> {
  const { i18n } = useTranslation();

  const schema = useMemo(() => schemaBuilder(t), [schemaBuilder, t]);

  const methods = useForm<TFormInput, TFormContext, TFormOutput>({
    ...formProps,
    resolver: zodResolver(schema)
  });

  const reset = methods.reset;

  useEffect(() => {
    void i18n.language;
    reset(undefined, { keepValues: true });
  }, [i18n.language, reset]);

  return methods;
}

Type error:

error TS2769: No overload matches this call.
  Overload 1 of 4, '(schema: Zod3Type<TFormOutput, TFormInput>, schemaOptions?: ParseParams | undefined, resolverOptions?: NonRawResolverOptions | undefined): Resolver<...>', gave the following error.
    Argument of type 'ZodType<TFormOutput, ZodTypeDef, TFormInput>' is not assignable to parameter of type 'Zod3Type<TFormOutput, TFormInput>'.
      Types of property '_def' are incompatible.
        Property 'typeName' is missing in type 'ZodTypeDef' but required in type '{ typeName: string; }'.
  Overload 2 of 4, '(schema: $ZodType<unknown, FieldValues>, schemaOptions?: ParseContext<$ZodIssue> | undefined, resolverOptions?: NonRawResolverOptions | undefined): Resolver<...>', gave the following error.
    Argument of type 'ZodType<TFormOutput, ZodTypeDef, TFormInput>' is not assignable to parameter of type '$ZodType<unknown, FieldValues>'.
      Property '_zod' is missing in type 'ZodType<TFormOutput, ZodTypeDef, TFormInput>' but required in type '$ZodType<unknown, FieldValues>'.

This minor version should not create any breaking changes. But it does.

dsiguero avatar Jun 10 '25 13:06 dsiguero

Hello, I'm having the same issue to when I upgraded @hookforms/resolvers from 5.0.1 to 5.1.0 (and still doesn't work with 5.1.1.

Type error: No overload matches this call.
Overload 1 of 4, '(schema: Zod3Type<{ name: string; email: string; password: string; }, { name: string; email: string; password: string; }>, schemaOptions?: ParseParams | undefined, resolverOptions?: NonRawResolverOptions | undefined): Resolver<...>', gave the following error.
Argument of type '{ errorMap: z.ZodErrorMap; }' is not assignable to parameter of type 'ParseParams'.
Type '{ errorMap: ZodErrorMap; }' is missing the following properties from type 'ParseParams': path, async
Overload 2 of 4, '(schema: $ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>, schemaOptions?: ParseContext<$ZodIssue> | undefined, resolverOptions?: NonRawResolverOptions | undefined): Resolver<...>', gave the following error.
Argument of type 'ZodObject<{ email: ZodString; password: ZodString; name: ZodString; }, "strip", ZodTypeAny, { name: string; email: string; password: string; }, { ...; }>' is not assignable to parameter of type '$ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>'.
Property '_zod' is missing in type 'ZodObject<{ email: ZodString; password: ZodString; name: ZodString; }, "strip", ZodTypeAny, { name: string; email: string; password: string; }, { ...; }>' but required in type '$ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>'.o.

Thanks in advance for your help 🙏

lauthieb avatar Jun 10 '25 20:06 lauthieb

I've looked into this a bit. My findings:

  1. The error in your codesanbox replicates with 5.0.1
  2. The code you pasted into your issue above doesn't error with either version

fyi It's a little confusing that the code is different between these and makes things harder to replicate/address

You're right that useForm doesn't seem to like being handed a union of Zod schema types. I'm looking for a better solution to this. But your approach of casting the schema with as z.ZodType<z.infer<typeof formResolver>> is a good workaround, and it's working for me with the latest versions of hookform. Here's a standalone code sample that doesn't throw any errors:

import { useForm } from "react-hook-form"; import "./styles.css"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod";

let renderCount = 0;

const createSchema = z.object({ slug: z.string().min(2, "Enter a valid slug"), });

const updateSchema = createSchema .extend({ cuid: z.string(), }) .partial() .required({ cuid: true, });

const isUpdate = true;

export default function App() { const schema = isUpdate ? createSchema: updateSchema; type schema = z.input;

type FormValues = z.infer; const { register, handleSubmit } = useForm({ resolver: zodResolver(schema as z.ZodType<z.infer>), }); const onSubmit = (data: FormValues) => console.log(data); renderCount++;

return (

  <form onSubmit={handleSubmit(onSubmit)}>
    <input {...register("cuid")} placeholder="Cuid" />
    <input {...register("slug")} placeholder="Slug" />

    <input type="submit" />
  </form>
</div>

); }

@colinhacks appreciate your work on Zod! Thank you for building it.

I updated the code to match what you shared in codesandbox, and I'm getting type errors. See below:

No overload matches this call.
  Overload 1 of 4, '(schema: Zod3Type<{ slug: string; } | { cuid: string; slug?: string | undefined; }, { slug: string; } | { cuid: string; slug?: string | undefined; }>, schemaOptions?: ParseParams | undefined, resolverOptions?: NonRawResolverOptions | undefined): any', gave the following error.
    Argument of type 'ZodType<{ slug: string; } | { cuid: string; slug?: string | undefined; }, ZodTypeDef, { slug: string; } | { cuid: string; slug?: string | undefined; }>' is not assignable to parameter of type 'Zod3Type<{ slug: string; } | { cuid: string; slug?: string | undefined; }, { slug: string; } | { cuid: string; slug?: string | undefined; }>'.
      Types of property '_def' are incompatible.
        Property 'typeName' is missing in type 'ZodTypeDef' but required in type '{ typeName: string; }'.
  Overload 2 of 4, '(schema: $ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>, schemaOptions?: ParseContext<$ZodIssue> | undefined, resolverOptions?: NonRawResolverOptions | undefined): any', gave the following error.
    Argument of type 'ZodType<{ slug: string; } | { cuid: string; slug?: string | undefined; }, ZodTypeDef, { slug: string; } | { cuid: string; slug?: string | undefined; }>' is not assignable to parameter of type '$ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>'.
      Property '_zod' is missing in type 'ZodType<{ slug: string; } | { cuid: string; slug?: string | undefined; }, ZodTypeDef, { slug: string; } | { cuid: string; slug?: string | undefined; }>' but required in type '$ZodType<unknown, FieldValues, $ZodTypeInternals<unknown, FieldValues>>'.typescript(2769)
zod.d.ts(16, 9): 'typeName' is declared here.
schemas.d.ts(85, 5): '_zod' is declared here.

djshubs avatar Jun 20 '25 17:06 djshubs

Zod3Type seems to be an internal @hookforms/resolver type introduced in 5.1.0 which does not match with ZodSchema (imported from zod).

In my case I want to pass through a ZodSchema via props and pass it to zodResolver which does not work anymore.

lgraubner avatar Jun 25 '25 14:06 lgraubner

I'm seeing the same issue. As @lgraubner pointed out, Zod3Type introduced in v5.1.0 does not match ZodSchema from zod. I looked into this a bit more and here’s a minimal reproducing example with some observations.

Steps to reproduce

Here is minimal reproducing code:

import * as z from "zod/v3" // 3.25.67
import { zodResolver } from "@hookform/resolvers/zod" // 5.1.1

function reproduceError(schema: z.ZodSchema): void {
  zodResolver(schema) // No overload matches this call. Overload 1 of 4, ...
  schema._def.typeName // Property 'typeName' does not exist on type 'ZodTypeDef'.
}

function noError(schema: z.ZodObject<any>): void {
  zodResolver(schema) // OK
  schema._def.typeName // OK
}
  • reproduceError triggers the same error reported by @djshubs
  • noError compiles without issues.

What's happening?

zodResolver's type signature relies on an internal interface:

// https://github.com/react-hook-form/resolvers/blob/master/zod/src/zod.ts

// minimal interfaces to avoid asssignability issues between versions
interface Zod3Type<O = unknown, I = unknown> {
  _output: O;
  _input: I;
  _def: {
    typeName: string;
  };
}

Because Zod3Type includes ._def.typeName, any type passed to zodResolver must have that property:

  • ZodObject does have ._def.typeName, so it type-checks.
  • The more general ZodSchema does not, hence the error.

Possible fixes

I'm not sure which direction is preferred, but two options seem plausible:

  1. Loosen Zod3Type: Drop typeName from Zod3Type's _def, making the constraint match ZodSchema.
  2. Tighten zodResolver: Restrict the schema parameter to ZodObject (and document it accordingly)

shwaka avatar Jul 01 '25 11:07 shwaka

In addition, here are some workarounds for this issue.

The root cause is that the ZodSchema type does not declare the _def.typeName property, which is expected by zodResolver via Zod3Type, even though it exists at runtime. To make zodResolver(schema) compile, we can help the type system by asserting that the schema has the required shape. This can be done in a few ways:

  • workaround1: Use an intersection type (or a type predicate)
  • workaround2: Specify the Def type parameter of ZodSchema
function workaround1(schema: z.ZodSchema & { _def: { typeName: string } }): void {
  zodResolver(schema)
}

type DefWithTypeName = z.ZodTypeDef & { typeName: string }

function workaround2(schema: z.ZodSchema<any, DefWithTypeName>): void {
  zodResolver(schema)
}

As far as I can tell, all real Zod schemas do have the _def.typeName property, so these type assertions should be safe for practical use.

shwaka avatar Jul 02 '25 11:07 shwaka

Are you planning fixing that?

pvarouktsis avatar Aug 19 '25 13:08 pvarouktsis

@pvarouktsis If the fixes go smoothly, I will create a pull request by this weekend.

shwaka avatar Aug 21 '25 10:08 shwaka

I’ve created a PR with the changes described above. As far as I can confirm, this PR resolves the errors you reported in the code examples above.

PR contents

Changes in the source code:

// Before change
interface Zod3Type<O = unknown, I = unknown> {
  _output: O;
  _input: I;
  _def: {
    typeName: string; // Remove this
  };
}
const isZod3Schema = (schema: any): schema is z3.ZodSchema => {
  return (
    '_def' in schema &&
    typeof schema._def === 'object' &&
    'typeName' in schema._def // Remove this
  );
};

// After change
interface Zod3Type<O = unknown, I = unknown> {
  _output: O;
  _input: I;
  _def: object;
}
const isZod3Schema = (schema: any): schema is z3.ZodSchema => {
  return '_def' in schema && typeof schema._def === 'object';
};

Added test:

  it('should accept z.ZodType', () => {
    // https://github.com/react-hook-form/resolvers/issues/782
    const schema: z.ZodType<{ id: number }> = z.object({ id: z.number() });
    const resolver = zodResolver(schema);

    expectTypeOf(resolver).toEqualTypeOf<
      Resolver<{ id: number }, unknown, { id: number }>
    >();
  });

Rationale for the change

As I mentioned in my previous comment, the issue was that the definition of Zod3Type included _def.typeName. I decided to simply remove this property for the following reasons:

  • zodResolver never uses typeName internally
  • To check whether it is v3, it is sufficient to see if _def exists; there is no need to rely on typeName
  • Before v4 support, zodResolver did not require typeName

Additional note

@colinhacks Could you confirm if my understanding above is correct?

shwaka avatar Aug 23 '25 05:08 shwaka