defaultValues should be typed as partial to do correct validation with standard schema
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
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 }}
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 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:
- You create a complex schema (say, 10 fields)
- You initialize your fields, pass the schema to onChange, you get proper validation
- You realize that one field is redundant as it is dependent on another field. You remove the field.
- When your schema now runs, it will complain that there is a string where it expected undefined (your now removed schema property). This works!
- 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.
- Your form is stuck in a bad state where
canSubmitis 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
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?
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.
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 :/
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.