Repository navigation
Description
Describe the bug
Previously with v0.42.x the types of useForm (react hook) and FormApi (core class) had 2 generic types, with only 1 being required:
TFormData,
TFormValidator extends Validator<TFormData, unknown> | undefined = undefined,Starting 0.43.0 however this has changed to 9 required generic types:
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>,
TOnServer extends undefined | FormAsyncValidateOrFn<TFormData>,Even if the form desired by the user does not have any validations they are now forced to define their strictly typed form like so:
const myForm = new FormApi<MyInterface, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined>Which is extremely cumbersome. Furthermore, for our work project we also wrap a few Tanstack Form component with our custom Material UI components and we want those to be strictly typed, which would now become a complete flustercluck (excuse my french) with generic types.
I would really appreciate it if the defaults for the generic types could be re-introduced.
Your minimal, reproducible example
Steps to reproduce
- Go to the above linked TypeScript playground
- Go to the "errors" tab
- See the error "Expected 9 type arguments, but got 1."
Code used on playground:
import { useForm } from '@tanstack/react-form';
interface MyData {
name: string;
age: number;
}
const myForm = useForm<MyData>({
defaultValues: {
age: 30,
name: 'Favna'
}
})Expected behavior
Expected behaviour is that the code above works just fine, regardless of the 8 missing generic types.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
- OS: MacOS
- Version: Seqouia 15.3.1
TanStack Form adapter
react-form
TanStack Form version
0.43.2
TypeScript version
5.7.3
Additional context
No response
Activity
crutchcorn commentedon Feb 24, 2025
This is correct behavior and is required for many of our TS types to work properly. PRs welcomed if you can solve our types another way.
crutchcorn commentedon Feb 24, 2025
We never intend anyone to pass anything to
useForm's generics. Instead, you should be doing:VibrantMoose commentedon Feb 24, 2025
In my case I don't pass generics to the hook, but I pass the myForm instance to react child components. I was using ReactFormExtendedApi in the children's props interfaces. I have multiple huge forms I had to split in multiple components, so the useForm hook is called in a parent component. Am I doing this wrong ?
EDIT: Oh.... I missed the useField hook I guess ?
crutchcorn commentedon Feb 24, 2025
Do not use the
useFieldhook, it's strongly suggested against using in your own codebase.We do not currently have a good alternative API to do what you're looking for, though I have an API in mind for how to do this. However, it's unclear to me some of the edgecases.
EG:
favna commentedon Feb 24, 2025
crutchcorn commentedon Feb 24, 2025
deniskabana commentedon Feb 25, 2025
@crutchcorn This is a very tough decision that you made for us.
Unfortunately, I second the OP. We never have any objects representing the data, as ALL of these in our entire app are received asynchronously during run-time. We however have full access to the interfaces that the objects adhere to during compile-time and for all TypeScript features our project HAS to rely on generics.
This decision seems very short-sighted. Required code in
0.43.x:OR
Can this issue be re-wised or re-visited please? Specifically for those of us who have no
defaultValueswhen compiling TS?crutchcorn commentedon Feb 25, 2025
This decision is the opposite of short-sighted; it's motivated by seeing our consistent shifts of generics and TS code in general. If we encouraged folks to use our generics, they'd break in a minor easily, even past v1.
Our goal is to create APIs that handle this usecase without the need of generics passing of any kind.
To that end, please review the APIs outlined in this newest PR, which I believe should handle the usage you need:
#1179
deniskabana commentedon Feb 25, 2025
Thank you for your response @crutchcorn. This does not address the lacking JS representation of any data during compile-time.
This code is from your PR's description:
We only have TS interfaces, no objects that would represent them run-time. These are constructed from the form itself the first time it is submitted. Until then, all we have is a TS interface we import. This makes it close to impossible to stick with
@tanstack/form-reactfor multiple projects I am working on.In
0.42.x, this is what our code looked like:crutchcorn commentedon Feb 25, 2025
@deniskabana I'm not following why the following does not work:
It works at compile time just fine.
deniskabana commentedon Feb 25, 2025
@crutchcorn This causes unexpected failures that TS does not prevent:
EDIT: This causes the TS to not be useful at all since it never flagged the code as incorrect.
crutchcorn commentedon Feb 25, 2025
That's a bug, and one I'm rather surprised to see given how extensive our type tests are for this usecase.
Can you create a minimal repro on an issue and I'll prioritize a fix/response?
deniskabana commentedon Feb 25, 2025
I'm not sure what the bug is here, as it is an expected TypeScript behavior with typecasting. Try this for yourself in TS:
This is why I keep pushing against your notion of not needing the generic types - typecasting foregoes any information about the shape of the object by design.
Edit: CC @crutchcorn
crutchcorn commentedon Feb 25, 2025
I'm fundamentally not understanding how a generic type fixes this.
Let's assume that you pass a generic as it was before and we """"fixed"""" this behavior differencial:
vs
Both of these values will behave identically at runtime:
The behavior is exactly the same at both compile time and runtime alike. The only difference is in DX and one of our core philosophies (even from the start) has been to avoid anyone passing anything to our generics.
Even from day 1 we always guided people away from using generics in our Discord, issues, and forums. For a short while, we had a few docs/test items that used this API, but they were errant and removed as soon as we caught them during reviews.
Again, if someone is able to make it such that you can pass a single generic and have all the inferencing still work we'd love a PR.
That said, it's not currently possible in TypeScript - it's an inherent limitation on the language:
And again, it's incredibly brittle in case we ever add a mandatory type in the future.
I understand y'all are frustrated, I empathize with this. Could I go back in time and make our boundaries and guidance more clear I would. Hindsight is 20/20. But for 99% of our users, I strongly believe this is the right way to go.
KGSands commentedon Feb 25, 2025
Previously for nested forms I would do the following e.g.:
And in the child prop:
where FormType was:
I guess I can get around the typing with:
But what is the child type now? I'm not sure.
edit: perhaps I have to wait on #1179
blipk commentedon Mar 1, 2025
I had no issue storing the useForm result in tanstack store and using it in all my components
crutchcorn commentedon Mar 1, 2025
Then continue doing so, just know that it's not an officially supported mechanism for Form, as you lose much of what makes TSF great.
Instead we suggest this:
https://tanstack.com/form/latest/docs/framework/react/guides/form-composition
onehorsetown commentedon Mar 24, 2025
Running into this issue also. I "simply" want to be able to properly type of the return type of
useFormat compile time.const form = useForm({ ... });
What is the type of
form? I understand that thewithFormHOC provides a mechanism to type theforminstance in a Component's child render function. But what I need is to infer the form's type not as a component prop, but as something I want to pass to a non-react utility function or my own React Context.const doSomethingOutsideOfReact = (form: ???, ...) => ...How to solve this? Well suffice it to say that entire Tanstack inferred typing is both truly amazing, but also well beyond my ability to comprehend. In TanStack router we know that it involves some black magic involving augmenting the RouterConfig type and in tRPC (not tanstack, but equally awesome inference), it involves the utility types
inferRouterInputs<T>andinferRouterOutputs<T>.https://trpc.io/docs/client/vanilla/infer-types
So perhaps I am asking for:
I understand that
FormApitakes 9/10 generic arguments for a reason so the above is likely wishful thinking.MinnDevelopment commentedon Mar 25, 2025
We use
ReturnTypeof our custom form hook initializer function as the form's type, this works if you don't require any dynamic default values and validators.craigbehnke commentedon Apr 27, 2025
I just tried to upgrade, and was blown away by this change. (Not in a good way)
We have a custom react hook that wraps the tanstack
useFormhook and then adds some additional functions and a FormWrapper component to the final returned type. This lets us centralize all form boilerplate into one hook that can then be imported into a bunch of different forms within the app.This change makes that untenable, since my understanding is that we would have to add all the new generics to the hook itself and then manually set those generics at every call site.
I don't want to have to do this (it would take a long time to achieve, if it is even possible), and so I checked the npm registry for the last version of the library before this change, and it seems that version is the 3rd most popular over the past week.
Evidently a lot of people, myself included, do not want to deal with the headaches this change introduces. Is there a way to make this change less absurd? Put another way specifically for my use case, is there a way to wrap the
useFormhook in another hook and have total type safety, just like before?Something like:
Is that possible right now, and if not, what would need to change?
crutchcorn commentedon Apr 27, 2025
Hey @craigbehnke , I can see you're frustrated. Here's the deal: I'm frustrated too. Your tone isn't appropriate and frankly your feedback is months late.
Please feel free to reopen a discussion elsewhere with a more inquisitive and polite response and I might reply.
Until then, please consider the folks on the maintaining side.
Have a good day!
craigbehnke commentedon Apr 27, 2025
@crutchcorn I'm not sure what in my comment warranted your snarky response.
Previously valid behaviors using the library broke without a workaround or migration path -- that's not something I'm used to in any tanstack library, and it frankly surprised me.
I went over what I have been doing up until now that was impacted by the change.
I highlighted that this is not an isolated issue, but affects many people that have not been able to update.
I then outlined what I wanted to be able to do and asked whether it was possible to a thread that, to be candid, was already discussing workarounds for the issue.
There was no language, and I did not personally insult you.
Please forgive my frustration that I had to deal with a breaking change raised by an automated branch on a Sunday.
craigbehnke commentedon Apr 28, 2025
@crutchcorn On further reflection, I apologize for any perceived slight against you or the project. My frustration is with the work suddenly on my plate, not you, and I recognize that you are likely working on this project on a volunteer basis.
With that in mind, I would greatly appreciate any input you may have into whether there is a migration path from the custom hook I outlined above to the current best practice for using the library. How can we get a fully typed form object out of the useForm hook (and a fully typed input object) without manually setting the 8-9 new generic types, which you have already said is not the intended use case?
crutchcorn commentedon Apr 28, 2025
@craigbehnke I'm both surprised and grateful that you've come back to this issue in such a positive light. I do appreciate it ✨ I'm also sorry you've gotten sudden work on your plate - that's genuinely never fun.
So yeah, here's the reason for the 9 generics: They're inferring things from the code you'll likely want to keep with any kind of wrapper around
useFormthat you'd make. This includes:onChange/onBlur/et al errorsdefaultValuesasFormDataThe way I'd work around this actually pretty heavily depends on what your goals are with
useCustomForm. What are you looking to extend fromformwith?I actually have an idea for how to add
useCustomFormmore easily, but worry that we may have already solved this withuseAppFormandcreateFormHookAPIs:https://tanstack.com/form/latest/docs/framework/react/guides/form-composition
Could you look through those docs real quick and if they don't suit your needs let me know what you're aiming to do?
CanRau commentedon Apr 28, 2025
Haven't followed this discussion closely and might be a little off here, doesn't something like what I'm doing here work?
chce commentedon Jul 4, 2025
This can be avoided by using satisfies. E.g. defaultValues: { name: {} satisfies Name as Name }