form icon indicating copy to clipboard operation
form copied to clipboard

defaultValues should be typed as partial to do correct validation with standard schema

Open ruudandriessen opened this issue 6 months ago • 5 comments

Describe the bug

Given a form with the following schema:

const schema = z.object({
   foo: z.string(),
   bar: z.number(),
})

We may not have a default value for foo and want to make sure the user selects a valid one. As such, we add this schema as validator together with the default values we do know (i.e. bar):

defaultValues: { bar: 42 }

This results in a type error. But, since we're not submitting anything yet - we are totally fine to not have a value for foo yet. Right now, the only solution would be to pass an empty string as default - but that would be able to pass when we'd call the validator, which isn't what is expected.

I suspect the fix would be to mark defaultValues as Partial<TFormData> instead of just TFormData.

Your minimal, reproducible example

https://www.typescriptlang.org/play/?#code/JYWwDg9gTgLgBAbwF4F84DMoRHA5EiAE1wG4AoUSWRAVwGcBTAMWhDU2zwAEYBDAOzp8AxgGsA9FAa9hMALTpWuMmWERB8OsIAWDELzgBeOEgB0EAEYArBrIAUCMnGeKIALhOmhUYPwDmdgCUADROzha8UB5m-DQgFgxQQaEogSr0zKwOYXDi4nAAKtrAdHB02hA0ADaEcAkYvgzBdTTwfgwwpb7oiVK1vKWKUDiEvHx1DL5+OXmIdZEesfGJcGi+QtK1EOhzrh7eUyTzUXBLCVCrOYQM6LzVMABqvFU0DHQejs5fxx4ALABMoS+KCBzgAbs9gKMYNB3ogcl91ABlGgWEDAGD7HR6Xig1Z42YAUQAHmBbDApnAIS83nAYRM6QBPMn9YRqKCESn0gzUqFlbH6Gb5IolU4QADucD8wDBtIMMGZDDgvWgzTovmESpgugwrDg0IMoo10CksiqjLg3V6DH6pSs9Hg2l4YMpESgOWRqPRmLgDipz1epRQH39NLhZitUAAPAqydt+bp9AA+VaBIwpz7fOA5FBkVJAA

Steps to reproduce

Pass defaultValues without the required property, get type errors. See typescript playground.

Expected behavior

I expected to be able to pass a partial to default values.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

macOS

TanStack Form adapter

None

TanStack Form version

1.12.0

TypeScript version

5.8.3

Additional context

No response

ruudandriessen avatar Jun 13 '25 09:06 ruudandriessen

One concern that comes up with this typing is that you can end up in deadlocks. Your schema will tell you that the field bar is erroring, but you don't actually have a field called bar. You will never be able to submit the form because of it.

It would need to be some type where properties can't simply be omitted, but still be able to be specified as null | undefined.

See this playground for clarification:

const schema = z.object({
   foo: z.string(),
   bar: z.number(),
   baz: z.object({
      foo2: z.string()
   })
})

type SchemaValues = z.input<typeof schema>;


// oops! I can simply omit properties in my form, easily causing bad state when errors are generated
const defaultValues: Partial<SchemaValues> = {}

// but at least foo and bar are undefineable and is convenient 
const defaultValues2: Partial<SchemaValues> = {
   foo: undefined,
   bar: undefined
}

// oops! foo2 is not covered by this type and will still be forced to be string:
const defaultValues3: Partial<SchemaValues> = { baz: { foo2: undefined }}

LeCarbonator avatar Jun 13 '25 11:06 LeCarbonator

Your schema will tell you that the field bar is erroring, but you don't actually have a field called bar. You will never be able to submit the form because of it.

That would indeed be the point? With more complex validation, it is very uncommon a user can submit with just the default values. For example, a similar thing would occur if I have a .min(3) on bar and don't use it. Maybe I am not understanding the internals completely though. Are the default values used to initialize the internal form state? And would not specifying a key mean we don't initialize that field?

It would need to be some type where properties can't simply be omitted, but still be able to be specified as null | undefined.

Ah yes, that's a fair point - I reckon it would need to be a type like I made here in this playground

ruudandriessen avatar Jun 13 '25 12:06 ruudandriessen

@ruudandriessen Interesting playground! I'll see if there's something that can be done.

As for this:

Your schema will tell you that the field bar is erroring, but you don't actually have a field called bar. You will never be able to submit the form because of it.

That would indeed be the point? With more complex validation, it is very uncommon a user can submit with just the default values.

What I meant to say is something along these lines:

  1. You create a complex schema (say, 10 fields)
  2. You initialize your fields, pass the schema to onChange, you get proper validation
  3. You realize that one field is redundant as it is dependent on another field. You remove the field.
  4. When your schema now runs, it will complain that there is a string where it expected undefined (your now removed schema property). This works!
  5. However, what if the structure is flipped - it complains that it expected a string and received undefined (because the field does not even exist?) Tanstack Form will map the error to the field even though it doesn't exist, causing the error to be stored internally but invisible to the user.
  6. Your form is stuck in a bad state where canSubmit is false and there are errors, but the errors can never be resolved as the user has no access to them.

Shortened playground of the issue: Link

LeCarbonator avatar Jun 13 '25 12:06 LeCarbonator

I am not sure if I follow what the exact problem is in the playground you linked? If you remove a field in the schema, it also won't be used in the validator right, so it won't result in any sort of errors?

ruudandriessen avatar Jun 13 '25 12:06 ruudandriessen

I am not sure if I follow what the exact problem is in the playground you linked? If you remove a field in the schema, it also won't be used in the validator right, so it won't result in any sort of errors?

Here's what zod does with the schema

Zod also does not have passthrough by default, so any additional properties will cause errors.

LeCarbonator avatar Jun 13 '25 12:06 LeCarbonator

I've tried to handle this situation using Partial today and ... it's seems to not be a very good idea. After using Partial, when you access your form values (with a useStore for example), they're all ... | undefined because of the partial :/

Nabellaleen avatar Nov 26 '25 21:11 Nabellaleen

I don't think there's anything we can do here. We do not want to mark defaultValues as partial, since otherwise you're lying to TypeScript about the runtime shape, which would lead to a ton of bugs we cannot control.

crutchcorn avatar Nov 30 '25 07:11 crutchcorn