Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by NebulousCaptain633

How can I set an empty string as the default for an optional number field with React Hook Form and Zod?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I have a form field that may either have no input or must contain a number.

I defined my Zod schema to accept either an empty string literal or a number. However, when I set the default value to an empty string ("") for my property optionalNumber in React.useForm(), TypeScript complains that a string is not assignable to type number. Also, when used with react-hook-form, a runtime warning appears about converting controlled components into uncontrolled ones if default values are not provided properly.

Here is my schema:

JAVASCRIPT
const mySchema = z.object( optionalNumber: z.literal('') .or(z.coerce.number()) .transform((val) => val === '' ? undefined : val) );

And the problematic default value assignment:

TYPESCRIPT
const form = useForm(z.infer<typeof mySchema>({ resolver: zodResolver(mySchema), defaultValues: { optionalNumber: '' // TS2322: Type string is not assignable to type number } }));

The issue arises because the defaultValues parameter expects the input type, but z.infer returns the output type due to z.transform(). As suggested by some comments, using z.input explicitly tells TypeScript that the default value matches the input type. This allows the field to initialize with no input ("") and later handle numeric input properly.

Is there a recommended approach to initialize a react-hook-form field with an empty string for a Zod-typed optional number property, without triggering TypeScript errors and runtime warnings?

EDIT: As pointed out, once z.transform() is used, you need to distinguish between the input and output types. Using z.input is a viable solution:

JAVASCRIPT
price: z.string().optional().transform((val) => { if(val === '' || typeof val === 'undefined') { return undefined; } else { return parseInt(val, 10); } }).refine((val) => typeof val === 'undefined' || val >= 0, { message: "Price must at least be 0" })

And in React.useForm():

TYPESCRIPT
const form = useForm( z.input<typeof mySchema>({ // <== z.input ensures the default value matches the schema’s input type resolver: zodResolver(mySchema), defaultValues: { optionalNumber: '' } });
1

Comments 0

Answers 2

Views 89

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is one common solution: since your schema transforms the input value, you should explicitly supply the input type to React Hook Form’s generic argument. This way, the type of the default value is inferred based on the schema’s input rather than its output. For example, you can do this:

TYPESCRIPT
const form = useForm<z.input<typeof mySchema>>({ resolver: zodResolver(mySchema), defaultValues: { optionalNumber: '', }, });

Here’s what is happening:

  • Your Zod schema accepts either an empty string or a number as input. However, because you use z.transform(), the inferred type from z.infer is the output type (e.g. number | undefined).
  • By wrapping your schema with z.input, you explicitly tell TypeScript that the default values correspond to the input type (which allows for an empty string), avoiding the type conflict.

This approach lets your form field initialize to no input (""), and then later accept numeric input, while keeping both zod and react-hook-form happy with the types.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by NebularExplorer565 1 month ago

0

z.infer<typeof schema> will always resolve to the output of the provided zod schema (z.output<typeof schema>). Using z.input<typeof schema> i.e. useForm<z.input<typeof schema>>(...) will resolve to your expected types.

No comments yet.

Discussion

No comments yet.