T
TanStack7mo ago
extended-salmon

How do I type my form if I need to pass it to other components?

Before in 0.40 I was doing:
form: ReturnType<typeof useForm<DocumentFormData>
form: ReturnType<typeof useForm<DocumentFormData>
` But now that doesn't work and I don't want to have to type every single type of the 10 types FormApi requires... If it can be avoided
41 Replies
extended-salmon
extended-salmonOP7mo ago
literally just read the post below this my bad sorry let me close it
old-apricot
old-apricot7mo ago
You want to use our upcoming API for withForm: https://github.com/TanStack/form/pull/1179
GitHub
[WIP] Reusable app hook by crutchcorn · Pull Request #1179 · TanSta...
This has been cooking in the oven for a long time - I&#39;m extremely sorry it took me so long to figure out the direction we wanted to go in. This PR introduces three new APIs to help with: ...
old-apricot
old-apricot7mo ago
Oh lol you good
extended-salmon
extended-salmonOP7mo ago
The other guy's title is very bad in my defense XD @crutchcorn is the API released? What do I do in the mean time?
robust-apricot
robust-apricot7mo ago
the api is marked as draft, but I can confirm that it is useable in its current state
old-apricot
old-apricot7mo ago
API isn't released, but it should be stable. We're just waiting on docs and final community feedback So you can use that PR version of you'd like for sure, but there's also nothing wrong with staying at 0.44 (was it?) and upgrading once we merge
extended-salmon
extended-salmonOP7mo ago
Ok I can I do that then. Thank you! While I have you one quick question. Is there a way I can set the entire form? Like setFieldValue but for the entire form @crutchcorn
old-apricot
old-apricot7mo ago
That would be tricky for us. There's not. What are you trying to do?
extended-salmon
extended-salmonOP7mo ago
Well, I'm trying to hack a solution to a bug report I submitted: https://github.com/TanStack/form/issues/1171 Its blocking an important feature so I was thinking of trying to set all the form values instead of the array or index value and see if that worked. I can confirm it doesn't, I found out you can use reset to set all the field values but it still same behaviour
GitHub
Removal of objects from nested arrays doesn't work as expected · Is...
Describe the bug When removing an object from a nested array only the "id" field is removed from the element. This happens both if you use arrayField.removeValue and form.setFieldValue. Y...
old-apricot
old-apricot7mo ago
@Leonardo wanna look into the removal of objects from nested arrays issue?
extended-salmon
extended-salmonOP7mo ago
it seems to be an error with whatever you are doing to reconcile the original structure and the new one that overrides it
robust-apricot
robust-apricot7mo ago
this might explain a bug we‘ve encountered before when calling swapValues I might take a look at it tomorrow, perhaps it‘s something that can be fixed in a few lines the bug was about swapping two objects of different kinds and failing at runtime
extended-salmon
extended-salmonOP7mo ago
It might be related you are right. Its definitely something wrong in the process that merges the old form values and the new form values. Because the bug appears if you do any of: - arrayField.removeValue() - arrayField.setValue() -> not sure if this is the api but I mean setting the entire array with the filtered item removed - parentFieldOfArray.setValue() -> same as above - form.reset(entireFormValuesWithFilteredItem)
robust-apricot
robust-apricot7mo ago
what about a callback function as updater? instead of passing an array, pass (inputArray) => newArray to the method have you given that a try?
extended-salmon
extended-salmonOP7mo ago
No I haven't but give me a second and I can! Yes same issue Yep Same issue with this code:
form.setFieldValue(
`consignments[${index}].goodsItems`,
(oldValues) => {
return oldValues.filter(
(x, i) => i !== itemIndex
);
}
form.setFieldValue(
`consignments[${index}].goodsItems`,
(oldValues) => {
return oldValues.filter(
(x, i) => i !== itemIndex
);
}
robust-apricot
robust-apricot7mo ago
:HmmNotes:
extended-salmon
extended-salmonOP7mo ago
😂 Oh man, this bug is really killing me. But I got more information for you @Luca | LeCarbonator I would have to verify it with a minimal example but it appears that if you set the entire array with objects that are very different there is no issue. Exactly what that means I would need to do more testing
robust-apricot
robust-apricot7mo ago
not sure what you mean. Like creating a whole new array instead of shallow copying the first one? that could help, actually. Maybe the object reference is the breaking part your example seems a bit iffy. You have a Field with the state value, but access the state from the form level instead
robust-apricot
robust-apricot7mo ago
LeCarbonator
StackBlitz
Removal of objects from nested arrays doesn't work as expected (for...
Run official live example code for Form Simple, created by Tanstack on StackBlitz
robust-apricot
robust-apricot7mo ago
oh dear, I broke something :KEKWait: there we go something I've yet to figure out is why passing item.id as key crashes it on click, while itemIndex is valid
extended-salmon
extended-salmonOP7mo ago
Because somehow it generates objects without the index Like instead of removing the item it first removes it then on a rerun it adds it back again with id missing
extended-salmon
extended-salmonOP7mo ago
Look at these logs: - The top one first component render (not like initial just in response to an action) - The bot one is the component rerenders and you can see that the library added back in a bunch of weird objects with key values that probably correspond to some of the objects that were removed
No description
extended-salmon
extended-salmonOP6mo ago
If you need I'm happy to jump on a call and explain further @crutchcorn I am reading through the docs on the form composition and I must be missing something because this is incredibly inconvenient. If I am understanding it correctly. I need to define the form options outside of the component so I can import them into other components and spread them. But those options are not even used they are just for typesafety, so we have replaced typing a generic with having to import the form options everywhere and spreading them into the withForm HOC? We are creating a copy of the form options object for each sub component in the form just for type safety? Also, I'm using react query mutations for the submission of my forms. Which means I have to do:
export const documentFormOptions = formOptions({
defaultValues: {} as DocumentFormData,
onSubmit: async () => {},
})

// inside my form component
function MyForm({defaultData}: {defaultData: DocumentFormData) {
const form = useAppForm({
...documentFormOptions,
defaultValues: defaultData, // here I have to override the object because the data is fetched.
onSubmit: async ({ value }) => { // here I have to override the onSubmit bc I am using mutation
await confirmDocumentMutation.mutateAsync({
data: {
id: document.id,
title: value.title,
type: value.type,
invoice: value.invoice,
},
})
},
})

return (
<form>
{".... my form"}
</form>)
}
export const documentFormOptions = formOptions({
defaultValues: {} as DocumentFormData,
onSubmit: async () => {},
})

// inside my form component
function MyForm({defaultData}: {defaultData: DocumentFormData) {
const form = useAppForm({
...documentFormOptions,
defaultValues: defaultData, // here I have to override the object because the data is fetched.
onSubmit: async ({ value }) => { // here I have to override the onSubmit bc I am using mutation
await confirmDocumentMutation.mutateAsync({
data: {
id: document.id,
title: value.title,
type: value.type,
invoice: value.invoice,
},
})
},
})

return (
<form>
{".... my form"}
</form>)
}
I'm going crazy, there is no way this is the recommended way of doing things. Having to duplicate parameters like this seems awful and super prone to error. Then I have to continue this pattern of giving bogus inputs and typecasting them to something else to actually get the typesafety I want. For example:
export const DocumentDetailsTab = withForm({
...documentFormOptions,
props: {
relatedDocuments: '' as unknown as DocumentFormData[], // this data is not available until the parent component renders but to get typesafety on it I have to give it a random value and cast it to what it needs to be
},
render: function DocumentDetailsTab({ relatedDocuments, form }) {
....}})
export const DocumentDetailsTab = withForm({
...documentFormOptions,
props: {
relatedDocuments: '' as unknown as DocumentFormData[], // this data is not available until the parent component renders but to get typesafety on it I have to give it a random value and cast it to what it needs to be
},
render: function DocumentDetailsTab({ relatedDocuments, form }) {
....}})
I hope I am not sounding in any way rude. I just can't believe this would be the recommended approach
old-apricot
old-apricot6mo ago
I'm going crazy, there is no way this is the recommended way of doing things.
I just can't believe this would be the recommended approach
I hope I am not sounding in any way rude.
If you can think of alternative ways to do things without requiring a generic passed anywhere and still inferring all 9 values in withForm, lmk Until then, see above
robust-apricot
robust-apricot6mo ago
the withForm looks about right, though you have an unnecessary as unknown in there. If you set an initial value of [] instead of ““ TS probably wouldn‘t complain
correct-apricot
correct-apricot6mo ago
just create another function that will return form, after that type ReturnType<typeof getForm> where getForm is the functino that creates the form. I am currently doing this and it works perfectly fine
robust-apricot
robust-apricot6mo ago
do you have an example?
correct-apricot
correct-apricot6mo ago
No description
correct-apricot
correct-apricot6mo ago
No description
correct-apricot
correct-apricot6mo ago
No description
correct-apricot
correct-apricot6mo ago
No description
noble-gold
noble-gold6mo ago
Curious if there’s issues using this. This is actually more typesafe atm due to this bug https://github.com/TanStack/form/issues/1273 seems a lot easier than having to use the HOC and formOptions
GitHub
Missing type inference with Validators in formOptions · Issue #1273...
Describe the bug Currently, defining validators within the formOptions function does not allow proper type inference from defaultOptions. To retain type inference, users are forced to define valida...
extended-salmon
extended-salmonOP6mo ago
After migrating a significant portion of the codebase to this new pattern I will say that it is not as bad as I thought initially. Its quite a bit more verbose than I would like but it works fine. I think the worse part is the withForm HOC. Its really ugly (IMHO) to have to proxy the types for the additional props in such a way. Also really it is infering the type from the form options which will not contain the complete form type in most cases since you most likely do something like this:
const formOps = formOptions({
// define a few stuff here, mainly the defaultValue type
})

// inside your component
const form = useAppForm({
...formOps,
// define validators and onSubmit here (maybe even other things)
})
const formOps = formOptions({
// define a few stuff here, mainly the defaultValue type
})

// inside your component
const form = useAppForm({
...formOps,
// define validators and onSubmit here (maybe even other things)
})
Which doesn't feel great. In the end anywhere you use the withForm HOC you are just infering the types based on the formOptions in a, arguably, more verbose and unintuitive way than doing something like:
type formType = inferForm<typeof formOps>
type formType = inferForm<typeof formOps>
I'm not a ts wizard but I cannot see any benefit to the HOC over doing something like this. @Choco Unfortunaly, for me this is also a lot of work for me since building my form has a lot of dependencies. But I think it is still less work than wiring all the HOC's And it seems to work with useAppForm also
noble-gold
noble-gold6mo ago
Curious what you mean by this? I don't understand why adding 1 hook would cause issues? What do you mean by dependencies?
extended-salmon
extended-salmonOP6mo ago
Its not that big of an issue, its just that I have to pass a lot of stuff to the hook (i.e it needs to take like 5+ params instead of 0) so it's a little annoying. But it works well I have refactored everything with this approach and it is much nicer than the proposed approach
noble-gold
noble-gold6mo ago
Nice, yeah that makes sense if your formOptions do require parameterized values
genetic-orange
genetic-orange6mo ago
Honestly I would happily have an API that takes 20+ Generics if it meant I could wrap that for my own internal use and get type safety back I suppose I could do this in userland and just not have type safety INSIDE of my solution
correct-apricot
correct-apricot6mo ago
Right now I’m working around it by any-casting the passed form into the withForm components and making the forms own defaulValues by combining the child/sub forms formOptions. That way, at least the partials are internally typed and the form takes the types from them. To make subforms I take an extra name prop and use use an extra function getName(name: typeof formOpts.defaultValues): any to make the AppFields names inside. Again, that at least gives internal safety as long as the function is used. I do have sone ideas I want to try to get something mire integrated for that but believe that they all depend in first solving the issues with passing extending forms without any.
noble-gold
noble-gold6mo ago
Important to note inference doesn’t ONLY come from the default values. Also from the validators and listeners(coming soon I think). So doing those strategies may work but runtime may differ from the types that are inferred. Which is why @Choco ‘s suggestion is technically more typesafe since you’re inferring everything
noble-gold
noble-gold6mo ago
GitHub
formOptions missing type inference leads to incorrect inferred type...
Describe the bug Currently, defining validators within the formOptions function does not allow proper type inference from defaultOptions. To retain type inference, users are forced to define valida...
correct-apricot
correct-apricot6mo ago
Hmm, I'll have to try that. I consider my current "solution" to be more as a temporary workaround to something I expect (or at least hope) to be enabled in a similar way without it soon-ish. The way I see it, its mostly just that strange reverse typing thing going on with passing extending forms to withForm components holding things back - which technically isn't even exclusive to withForm, it would happen all the same with manually typed FormApis. Once that is solved, I'd like to think that adding some sort of subform selector should be much more straightforward. Besides, I kind of ended up mostly putting validators on the AppFields anyways 😄 Might not be the best way but it works quite nicely with what I'm doing right now.

Did you find this page helpful?