resolvers
resolvers copied to clipboard
issue: Conditional/dynamic schema resolution no longer working
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
I've looked into this a bit. My findings:
- The error in your codesanbox replicates with 5.0.1
- 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>
);
}
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.
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 🙏
I've looked into this a bit. My findings:
- The error in your codesanbox replicates with 5.0.1
- 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
useFormdoesn'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 withas 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.
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.
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
}
reproduceErrortriggers the same error reported by @djshubsnoErrorcompiles 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:
ZodObjectdoes have._def.typeName, so it type-checks.- The more general
ZodSchemadoes not, hence the error.
Possible fixes
I'm not sure which direction is preferred, but two options seem plausible:
- Loosen
Zod3Type: DroptypeNamefromZod3Type's_def, making the constraint matchZodSchema. - Tighten
zodResolver: Restrict theschemaparameter toZodObject(and document it accordingly)
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 theDeftype parameter ofZodSchema
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.
Are you planning fixing that?
@pvarouktsis If the fixes go smoothly, I will create a pull request by this weekend.
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:
zodResolvernever usestypeNameinternally- To check whether it is v3, it is sufficient to see if
_defexists; there is no need to rely ontypeName - Before v4 support, zodResolver did not require
typeName
Additional note
@colinhacks Could you confirm if my understanding above is correct?