#Recommended pattern for required fields that must start as undefined in defaultValues?

16 messages · Page 1 of 1 (latest)

fierce loom
#

Migrating forms to TanStack Form. Common scenario: fields that are required on submit but have no valid initial value (e.g. a Date picker the user must choose, or a select that maps to a Zod enum).

Since TanStack Form infers field types from defaultValues, setting endDate: undefined or product: undefined makes the inferred type undefined — not Date or string. This creates a mismatch with the validation schema where these fields are required.

I can see two workarounds, both with significant drawbacks:

Option 1: Type-cast defaultValues
const defaultValues = {
product: undefined,
endDate: undefined,
startDate: new Date(),
} satisfies Partial<FormValues> as unknown as FormValues
Gets the types to line up, but the double-cast (as unknown as) is messy and still isn't perfect — you're lying to the type system and have to remember the field is actually undefined at runtime despite what the types say.

Option 2: Weaken the schema to match the initial state
Instead of z.date(), use z.string() with z.iso.date() and parse it. Instead of z.enum([...]), use z.string().refine(x => MyEnum.parse(x)) or similar. This avoids the type mismatch by making the schema's input type string (which can start as ""), but:

  • You lose the strong enum/Date typing at the form field level
  • It pushes validation complexity into the schema just to work around a defaultValues limitation
  • For dates specifically, locking into ISO string representation feels limiting compared to working with Date objects directly

The core question: Is there a recommended first-class pattern for "this field starts empty but must be present and valid on submit"? Something like a type-level split between defaultValues type and onSubmit type, or a built-in initiallyOptional concept? Or is one of the above workarounds the intended approach?

elder acorn
#

The validation schema and the defautl values are different things, and capable of being different. but they must be compatible. That is, the type of the schema must be assignable to the type of the form.

#

So, allowing undefined in defaultValues (since undefined is a valid storage type), but disallowing it in the validator, works fine.

fierce loom
#

The problem with that is the the onSubmit(values) type is the defaultValues which is incorrect as at this point it has been validated and is now the type of the validator.

sinful niche
# fierce loom Migrating forms to TanStack Form. Common scenario: fields that are required on s...

the standard schema support expects the schema to be a pipeline. The input type should reflect what the form starts out with, while the validation happens inside of the schema.

Works well for cases like valibot, but zod has a different philosophy of writing what you expect the output to be, rather than writing the transformation from input to output.

In this case, you can write a nullableInput helper which accepts a zod schema and makes it .nullable().refine(v => v !== null, 'message')

sinful niche
#

there's no need for safeParse since your validator schema ensured it mustn't fail

fierce loom
#

That is really helpful so really the only option at the moment is have an input type and then re parse the value to get the right type after onSubmit or do the casting / effective hack to pipe with zod?

Even with the valibot piping you would still have the same issue onSubmit not being the validated type right? Is this something that is going to be added in the future it feels like quite a big issue for anything that isn't a string | number in the form?

sinful niche
fierce loom
#

Thanks, I appriciate it.
Is the way you suggested (with input type and then re parsing in the onSubmit function) the currently recommended way to get around this or is it just to cast dateField: undefined as unknown as Date type thing in the default values the more ideal way of handling this situation?

If the re parsing in onSubmit is the best way is there a way to do this with zod?

sinful niche
#

it is recommended, yes

sinful niche
#

so for zod that would just be z.parse

fierce loom
#

Okay so avoid doing the casting basically? And have an input type and a validator that you use to z.parse on the onSubmit okay thanks I’ll try that out

#

Is there anything wrong using z.pipe? For the validator? So that you can have input type and output type out of one zod schema

sinful niche
#

I‘m not familiar with that function