resolvers icon indicating copy to clipboard operation
resolvers copied to clipboard

issue: [Zod v4] Typescript error when using zodResolver with z.coerce

Open quocluongha opened this issue 5 months ago • 3 comments

Version Number

5.1.1

Codesandbox/Expo snack

https://codesandbox.io/p/sandbox/dark-cookies-5xmdf4

Steps to reproduce

  1. Define a schema that has z.coerce
  2. Use useForm with the generic type parameter that is returned by z.infer
  3. Register resolver through zodResolver
  4. Observe error in the resolver

Expected behaviour

It shouldn't show the Typescript error.

There were 2 closed issues related to this #751 and #743

It seems there is a input type mismatch in the conversion as the zod v4 introduce a change to the input type when using coerce

What browsers are you seeing the problem on?

Chrome

Relevant log output

Type 'Resolver<{ height?: unknown; weight?: unknown; }, any, { height?: number | undefined; weight?: number | undefined; }>' is not assignable to type 'Resolver<{ height?: number | undefined; weight?: number | undefined; }, any, { height?: number | undefined; weight?: number | undefined; }>'.
  Types of parameters 'options' and 'options' are incompatible.
    Type 'ResolverOptions<{ height?: number | undefined; weight?: number | undefined; }>' is not assignable to type 'ResolverOptions<{ height?: unknown; weight?: unknown; }>'.
      Type 'unknown' is not assignable to type 'number | undefined'.ts(2322)

Code of Conduct

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

quocluongha avatar Jun 09 '25 15:06 quocluongha

Short answer: don't pass an explicit generic to useForm<> anymore. The schema types are all inferred now. Unfortunately the code example from the website (useForm<z.infer<typeof schema>>(...)) is unsound and assumes the input & output types are identical. When this isn't the case, you'll get errors like the ones you're experiencing. The Zod 4 changes to z.coerce change is now revealing this issue for more people. Drop the explicit generic and everything will work.

colinhacks avatar Jun 09 '25 20:06 colinhacks

@colinhacks Thanks, removing the generic type passed to useForm did clear the error. However the returned values from useForm are not properly typed anymore. For example: form.getFieldState doesn't have the field name typed as passing the generic type. The same happens with control, which makes the type for the field become any.

  • This is not passing the generic Image
Image
  • This is passing the generic Image
Image

quocluongha avatar Jun 09 '25 21:06 quocluongha

Removing the generic works for me as well. However type for my number input doesn't resolve correctly.

Type '{ onChange: (...event: any[]) => void; onBlur: Noop; value: unknown; disabled?: boolean | undefined; name: "age"; ref: RefCallBack; type: "number"; placeholder: string; }' is not assignable to type 'InputHTMLAttributes<HTMLInputElement>'.
  Types of property 'value' are incompatible.
    Type 'unknown' is not assignable to type 'string | number | readonly string[] | undefined'. [2322]

My input component:

<Input type='number' placeholder="shadcn" {...field} />

wengkhing avatar Jun 11 '25 00:06 wengkhing

cc @quocluongha @wengkhing I believe all of this is working as intended. These inferred types are correct.

Image

If you want to modify the input type for a coerced schema, you can do this in Zod 4:

z.coerce.string<string>()
// input and output type are both `string`

If you believe there is any incorrect behavior, please open a new issue with a fresh repro. 👍

colinhacks avatar Jun 21 '25 17:06 colinhacks