#How do I type my form if I need to pass it to other components?
70 messages · Page 1 of 1 (latest)
You want to use our upcoming API for withForm:
Oh lol you good
The other guy's title is very bad in my defense
XD
@blissful plank is the API released? What do I do in the mean time?
the api is marked as draft, but I can confirm that it is useable in its current state
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
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
@blissful plank
That would be tricky for us. There's not. What are you trying to do?
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
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...
@boreal swallow wanna look into the removal of objects from nested arrays issue?
it seems to be an error with whatever you are doing to reconcile the original structure and the new one that overrides it
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
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)
what about a callback function as updater? instead of passing an array, pass (inputArray) => newArray to the method
have you given that a try?
Yep Same issue with this code:
form.setFieldValue(
`consignments[${index}].goodsItems`,
(oldValues) => {
return oldValues.filter(
(x, i) => i !== itemIndex
);
}

😂
Oh man, this bug is really killing me. But I got more information for you @vivid owl 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
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
https://stackblitz.com/edit/tanstack-form-6yjqax4x?file=src%2Fminimal.tsx consider this fork
oh dear, I broke something 
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
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
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
If you need I'm happy to jump on a call and explain further
@blissful plank 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>)
}
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 }) {
....}})
I hope I am not sounding in any way rude. I just can't believe this would be the recommended approach
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
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
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
do you have an example?
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
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)
})
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>
I'm not a ts wizard but I cannot see any benefit to the HOC over doing something like this.
@peak gyro 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
Curious what you mean by this? I don't understand why adding 1 hook would cause issues? What do you mean by dependencies?
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
Nice, yeah that makes sense if your formOptions do require parameterized values
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
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.
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 @peak gyro ‘s suggestion is technically more typesafe since you’re inferring everything
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.