`any` type in form validators when using formOptions
Describe the bug
const formOpts = formOptions({
defaultValues: {
description: "",
},
validators: {
onSubmit: data => { //// HERE data IS any
console.log("validando", data); // OUTPUT: { value: { description: "" }, formApi: {...
}
}
});
Your minimal, reproducible example
. just copy the code above
Steps to reproduce
- Copy and paste this code in your editor, see the type of data
const formOpts = formOptions({
defaultValues: {
description: "",
},
validators: {
onSubmit: data => { //// HERE data IS any
console.log("validando", data); // OUTPUT: { value: { description: "" }, formApi: {...
}
}
});
Expected behavior
I expected to infer the type
How often does this bug happen?
Every time
Screenshots or Videos
Platform
- linux
TanStack Form adapter
react-form
TanStack Form version
v1.14.1
TypeScript version
v5.8.3
Additional context
Maybe related to https://github.com/TanStack/form/issues/1583
The formOptions function is a simple helper designed for creating reusable configurations.
A practical example is when you want to split a large form across multiple files. In that scenario, you can use formOptions to define a shared configuration that is then passed to other utilities, such as withForm.
https://tanstack.com/form/latest/docs/framework/react/guides/form-composition#breaking-big-forms-into-smaller-pieces
The type is loose
export declare function formOptions<T extends Partial<FormOptions<any, any, any, any, any, any, any, any, any, any>>>(defaultOpts: T): T;
Since inferencing with withForm will break if this is changed.
formOptions should generate the form options you give it. At the moment, it appears to be limited, which defeats its purpose.
I see that overriding values can be problematic, which thankfully is covered by unit tests.
The concern is:
const formOpts = formOptions({
defaultValues: { firstName: '' } as Person,
// if it infers Person here
validators: ({ value }) => {
return somethingElse(value)
}
})
const Component = withForm({
...formOpts,
// then this would break
defaultValues: { firstName: '', lastName: '' } as ExtendedPerson
})
@kusiewicz as @LeCarbonator said, the problem is when you need to use formOptions like this:
const formOpts = formOptions({
defaultValues: {
description: "",
},
});
const form = useForm({
...formOpts,
validators: {
onSubmit: (data) => { // HERE data IS any
//
},
},
});
Right now, the type inference in form validators only works if you declare the defaultValues in the useForm itself, like this:
const form = useForm({
defaultValues: {
description: "",
},
validators: {
onSubmit: (data) => { // TYPE IS INFERRED CORRECTLY
//
},
},
});
I had a go trying to fix this but am coming up short :/
Changing formOption's type to this
export function formOptions<
TFormData,
TOnMount extends undefined | FormValidateOrFn<TFormData>,
TOnChange extends undefined | FormValidateOrFn<TFormData>,
TOnChangeAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnBlur extends undefined | FormValidateOrFn<TFormData>,
TOnBlurAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnSubmit extends undefined | FormValidateOrFn<TFormData>,
TOnSubmitAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnDynamic extends undefined | FormValidateOrFn<TFormData>,
TOnDynamicAsync extends undefined | FormAsyncValidateOrFn<TFormData>,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,
TSubmitMeta = never,
>(
defaultOpts: Partial<
FormOptions<
TFormData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TOnServer,
TSubmitMeta
>
>,
): Partial<
FormOptions<
TFormData,
TOnMount,
TOnChange,
TOnChangeAsync,
TOnBlur,
TOnBlurAsync,
TOnSubmit,
TOnSubmitAsync,
TOnDynamic,
TOnDynamicAsync,
TOnServer,
TSubmitMeta
>
> {
return defaultOpts
}
fixes the types but breaks spreading extra options in like so
useForm({
...formOpts,
defaultValues: {
firstName: 'John',
lastName: 'Doe',
age: 10,
})
Should the docs be changed to communicate that validators and listeners shouldn't be added to formOptions? Or does anyone have any other ideas on what I could try to fix this
@a-is-4-adam at least for overwriting with different values, it can be argued that it doesn't match what the formOptions generated. However, what happens if you try to change the generated options with your implementation?
- FormOptions with only defaultValues, extend by adding validators
- FormOptions with validators, extend by overwriting validators
Looks like any validators can be added when only default values are added to formOptions are spread into useForm. When overwriting validators the specific validator, i.e onSubmit, has to conform to the same type as the original validator defined in the formOptions. Validators not defined in the formOptions can be defined in useForm without any problems.
I've put up a draft PR to show this working https://github.com/TanStack/form/pull/1679
One question, with this #1679 we shouldn't use validators and onSubmit in formOptions(), right ? I'm using v1.19.1
type RowsFormType = { id: string }[]
const formOpts2 = formOptions({
defaultValues: {
rows: [] as RowsFormType,
},
onSubmit: data => {
console.log(data);
},
validators: {
onSubmit: (data: { value: { rows: RowsFormType } }) => {
if (data.value.rows.length < 2) {
return "It should have at least one row";
}
},
},
});
function Afddd() {
const form = useAppForm(formOpts2); /// ERRORS
}
This is the error
1. tsserver: Argument of type '{ defaultValues: { rows: RowsFormType; }; onSubmit: (data: { value: { rows: RowsFormType; }; formApi: FormApi<{ rows: RowsFormType; }, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 9 more ..., unknown>; meta: unknown; }) => void; validators: { ...; }; }' is not assignable to parameter of type 'FormOptions<{ rows: RowsFormType; }, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., unknown>'.
Types of property 'onSubmit' are incompatible.
Type '(data: { value: { rows: RowsFormType; }; formApi: FormApi<{ rows: RowsFormType; }, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 9 more ..., unknown>; meta: unknown; }) => void' is not assignable to type '(props: { value: { rows: RowsFormType; }; formApi: FormApi<{ rows: RowsFormType; }, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 9 more ..., unknown>; meta: unknown; }) => any'.
Types of parameters 'data' and 'props' are incompatible.
Type '{ value: { rows: RowsFormType; }; formApi: import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormApi<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined...' is not assignable to type '{ value: { rows: RowsFormType; }; formApi: import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormApi<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined...'. Two different types with this name exist, but they are unrelated.
The types of 'formApi.options.defaultState' are incompatible between these types.
Type 'Partial<import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormState<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., import("/home/p...' is not assignable to type 'Partial<import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormState<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., import("/home/p...'. Two different types with this name exist, but they are unrelated.
Type 'Partial<import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormState<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., import("/home/p...' is not assignable to type 'Partial<import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormState<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., import("/home/p...'. Two different types with this name exist, but they are unrelated.
Types of property 'errorMap' are incompatible.
Type 'ValidationErrorMap<undefined, undefined, undefined, undefined, undefined, "It should have at least one row" | undefined, undefined, undefined, undefined, undefined> | undefined' is not assignable to type 'ValidationErrorMap<undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined> | undefined'.
Type 'ValidationErrorMap<undefined, undefined, undefined, undefined, undefined, "It should have at least one row" | undefined, undefined, undefined, undefined, undefined>' is not assignable to type 'ValidationErrorMap<undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined>'.
Type '"It should have at least one row" | undefined' is not assignable to type 'undefined'.
Type '"It should have at least one row"' is not assignable to type 'undefined'. [2345]
I think that if we are not supposed to use certain properties in formOptions we should remove them.
@a-is-4-adam @LeCarbonator see the error introduced in the latest version described in the previous message.
I updated to the latest version and still has not typed values in validators. So how it should look like?
Uh oh ... it looks like I was too eager with the merge there ... sorry about that! Will revert it in a bit. Looks like the unit tests in the PR didn't have the validator parameter, which breaks it.
We've have a pr up for review so we should have a fix out shortly, it would be a great help if you guys could check it out. π€ #1687
Hey there! I tried the last version and I get this new error running this code:
type RowsFormType = { id: string }[]
const formOpts2 = formOptions({
defaultValues: {
rows: [] as RowsFormType,
},
onSubmit: data => {
console.log(data);
},
validators: {
onSubmit: (data: { value: { rows: RowsFormType } }) => {
if (data.value.rows.length < 2) {
return "It should have at least one row";
}
},
},
});
function Afddd() {
const form = useAppForm(formOpts2);
return (
<form onSubmit={async e => {
e.preventDefault();
e.stopPropagation();
await form.handleSubmit(e); /// ERRORS HERE
}}>
//
</form>
);
}
The error message is this:
tsserver: Argument of type 'FormEvent<HTMLFormElement>' is not assignable to parameter of type 'never'. [2345]
@harry-whorlow
Hi @pedro757, why are you passing the submission event to the handleSubmit handler?
This is the function signature for handle submit.
The overrides prop is for submit meta, that you have not in your example defined. Hence why its type is never, and why it throws a Ts error
Hope this helps π
Here's another one:
From the example in my previous comment, if I delete the type:
type RowsFormType = { id: string }[]
const formOpts2 = formOptions({
defaultValues: {
rows: [] as RowsFormType,
},
onSubmit: data => {
console.log(data);
},
validators: {
onSubmit: (data) => { //TYPE DELETED HERE
if (data.value.rows.length < 2) {
return "It should have at least one row";
}
},
},
});
function Afddd() {
const form = useAppForm(formOpts2); /// NOW THIS ERRORS AGAIN
}
1. tsserver: Argument of type '{ defaultValues: { rows: RowsFormType; }; onSubmit: (data: { value: { rows: RowsFormType; }; formApi: FormApi<{ rows: RowsFormType; }, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 9 more ..., never>; meta: never; }) => void; validators: { ...; }; }' is not assignable to parameter of type 'FormOptions<{ rows: RowsFormType; }, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., never>'.
Types of property 'onSubmit' are incompatible.
Type '(data: { value: { rows: RowsFormType; }; formApi: FormApi<{ rows: RowsFormType; }, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 9 more ..., never>; meta: never; }) => void' is not assignable to type '(props: { value: { rows: RowsFormType; }; formApi: FormApi<{ rows: RowsFormType; }, FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 9 more ..., never>; meta: never; }) => any'.
Types of parameters 'data' and 'props' are incompatible.
Type '{ value: { rows: RowsFormType; }; formApi: import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormApi<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined...' is not assignable to type '{ value: { rows: RowsFormType; }; formApi: import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormApi<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined...'. Two different types with this name exist, but they are unrelated.
The types of 'formApi.options.defaultState' are incompatible between these types.
Type 'Partial<import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormState<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., import("/home/p...' is not assignable to type 'Partial<import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormState<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., import("/home/p...'. Two different types with this name exist, but they are unrelated.
Type 'Partial<import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormState<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., import("/home/p...' is not assignable to type 'Partial<import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormState<{ rows: RowsFormType; }, import("/home/myuser/Documents/myproject/node_modules/@tanstack/form-core/dist/esm/FormApi").FormValidateOrFn<{ rows: RowsFormType; }> | undefined, ... 8 more ..., import("/home/p...'. Two different types with this name exist, but they are unrelated.
Types of property 'errorMap' are incompatible.
Type 'ValidationErrorMap<undefined, undefined, undefined, undefined, undefined, "It should have at least one row" | undefined, undefined, undefined, undefined, undefined> | undefined' is not assignable to type 'ValidationErrorMap<undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined> | undefined'.
Type 'ValidationErrorMap<undefined, undefined, undefined, undefined, undefined, "It should have at least one row" | undefined, undefined, undefined, undefined, undefined>' is not assignable to type 'ValidationErrorMap<undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined>'.
Type '"It should have at least one row" | undefined' is not assignable to type 'undefined'.
Type '"It should have at least one row"' is not assignable to type 'undefined'. [2345]
Hi @pedro757, why are you passing the submission event to the handleSubmit handler?
This is the function signature for handle submit.
The overrides prop is for submit meta, that you have not in your example defined. Hence why its type is never, and why it throws a Ts error
Hope this helps π
My bad, I got confused with other form libraries, I believe the previous comment highlights a real problem
Can throw your example into a code sandbox, that would be really helpful... it's hard to gather whatβs going on without an example to see. π
Can throw your example into a code sandbox, that would be really helpful... it's hard to gather whatβs going on without an example to see. π
https://codesandbox.io/p/devbox/yfjvkd The important file to look at is client.tsx you'll see the type error there @harry-whorlow
Doesn't look like it's accessible. Did you make sure it's public? @pedro757
Sorry, now it's accessible @LeCarbonator
The overrides prop is for submit meta, that you have not in your example defined. Hence why its type is never, and why it throws a Ts error