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

70 messages · Page 1 of 1 (latest)

hasty mauve
#

Before in 0.40 I was doing:

  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

#

literally just read the post below this

#

my bad

#

sorry

#

let me close it

blissful plank
#

Oh lol you good

hasty mauve
#

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?

vivid owl
#

the api is marked as draft, but I can confirm that it is useable in its current state

blissful plank
#

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

hasty mauve
#

Ok I can I do that then. Thank you!

hasty mauve
#

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

blissful plank
#

That would be tricky for us. There's not. What are you trying to do?

hasty mauve
#

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

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...

blissful plank
#

@boreal swallow wanna look into the removal of objects from nested arrays issue?

hasty mauve
#

it seems to be an error with whatever you are doing to reconcile the original structure and the new one that overrides it

vivid owl
#

the bug was about swapping two objects of different kinds and failing at runtime

hasty mauve
# vivid owl this might explain a bug we‘ve encountered before when calling `swapValues` I m...

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)
vivid owl
#

have you given that a try?

hasty mauve
#

No I haven't but give me a second and I can!

#

Yes same issue

hasty mauve
vivid owl
hasty mauve
#

😂

hasty mauve
#

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

vivid owl
#

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

#

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

hasty mauve
#

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

hasty mauve
#

@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

blissful plank
#

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

vivid owl
peak gyro
#

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

peak gyro
royal quartz
hasty mauve
#

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

hasty mauve
#

And it seems to work with useAppForm also

royal quartz
hasty mauve
#

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

royal quartz
neon rampart
#

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

neon rampart
#

I suppose I could do this in userland and just not have type safety INSIDE of my solution

ebon adder
#

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.

royal quartz
#

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

ebon adder
#

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.