Re-using a generic sub-form agnostic of it's parent form gives TS errors
Hi!
First off: thanks for creating an amazing form library. I'm super happy with it so far! Really good stuff.
What I've stumbled across may just be a typescript bug. I'm not sure if it's me using the library wrong, if it's a bug or if it's simply not supported. But what I'm trying to do is to have two separate forms (with different schemas) re-use the same sub-form by leveraging the
withForm
-HoC.
When I try to do that I get a type error when passing the form to the child component. It seems it's not able to realize that the child form implements a sub-set of the parent's schema. Attaching a minimal example in the post below.
The forms both work just fine, it's just typescript yelling at me. Not sure if what I'm trying to do here is completely antipattern, but to my understanding this is how the composition library is meant to be used. I.e. the withForm
should be able to delivery generic sub-forms that can be plugged in anywhere. Or am I mistaken?9 Replies
metropolitan-bronzeOPβ’6mo ago
ambitious-aquaβ’6mo ago
the reason typescript complains is because subforms require strict equality
consider the following situation:
You create a subform that only needs one of the properties. Now you can call any form functions you'd like, including
onSubmit
and validators.
It makes no sense to then be able to reuse that with a different schema, as the values don't line up
of course, pragmatically it makes a lot of sense, you'd want to reuse sections of code multiple times.
I'm not sure what the solution is for that case. Perhaps it's the form group API they're working on. For now, you'll need to align the form types if you want to reuse the sectionsmetropolitan-bronzeOPβ’6mo ago
That makes sense i suppose. I'm curious as to what the recommended approach for this case is. I'd imagine this is a quite common use-case? Hopefully one of the maintainers see this and can chime in with some thoughts - I'd be really happy to understand the way this problem should be approached.
ambitious-aquaβ’6mo ago
I too am interested :PepeThumbs:
metropolitan-bronzeOPβ’6mo ago
I suppose I could elaborate a little more about the use-case as well. In case Iβm having the totally wrong approach here.
Weβre integrating towards an API.
The API has three endpoints:
- Create
- Update details
- Update relationships
The create endpoint requires us to submit both details and relationships upon creation. But when editing the entity we update details and relationships independently of each other. So what I hoped was to create a reusable subform for both details and relationships that can be be combined for the form used to create the entity, and also used independently/separately when updating the entity after the initial creation.
The create endpoint requires us to submit both details and relationships upon creation. But when editing the entity we update details and relationships independently of each other. So what I hoped was to create a reusable subform for both details and relationships that can be be combined for the form used to create the entity, and also used independently/separately when updating the entity after the initial creation.
ambitious-aquaβ’6mo ago
exactly our use case too. We often have small differences between creating forms and editing them
gradual-turquoiseβ’6mo ago
Iβve bern trying to make withForm take that for a while. This actually works for the outside prop but breaks the subforms internal form type: https://github.com/TanStack/form/pull/1334
As I still refuse to believe it canβt be made to work, Iβm just casting the parents form to any as I pass them in right now π
GitHub
Allow withForm components to accept extending properties by s-hoff ...
Replaces #1268.
This fails:
type Test<T> = {
defaultValues: T
fun: (props: { value: T }) => {}
}
const t: Test<{ firstName: string }> =
{} as Test<{ ...
metropolitan-bronzeOPβ’6mo ago
Yeah, I'm tempted to cast as well π
But that does feel like it truly goes against one of the core concepts of Tanstack form: it's type safety. Right now I've worked around this issue by simply implementing a component that's completely controlled from the outside by a field. And then I just use it in both the create and update form.
Something like this:
It works, it's just not as clean as being able to encapsulate all of this in a withForm component and then just reuse that entire thing. Now I also need to juggle more types, as
MyControlledComponent
needs to have its types explicitly defined rather than implied via the schemas as other fields have the luxury of.
And most importantly: right now I have the luxury of only having this problem with a single field (it's an array, truth be told). But if the child form were supposed to handle multiple fields my solution wouldn't scale nearly as well.gradual-turquoiseβ’6mo ago
Thatβs actually where I cast, to βonlyβ loose the types where the child is called. The child form is still just a normal
withForm
component.
The idea is that I donβt see hie whatever the solution will be could be that different, even if it turns out that it has to not be withForm. The any is just a cheap workaround to keep going in anticipation of it - fast to use and only loosing types on the simplest components I have, as all my parent forms are really doing is composing child forms and submitting.