T
TanStack7mo ago
deep-jade

Form Composition - withForm - TypeScript error

Hello! I apologise in advance for the length of this message. I am in the process of updating my applications to TanStack Form v1 and testing to fully understand the new features/APIs. Having seen that it is now possible to split up large forms using withForm (which is great), I decided to do a relatively simple test. Unfortunately, I am encountering a TypeScript error. So I would like to see if anyone knows if this is due to a problem of understanding on my part, or a potential bug that could be fixed.
29 Replies
deep-jade
deep-jadeOP7mo ago
form-context.tsx :
import React from 'react'
import { createFormHook, createFormHookContexts } from '@tanstack/react-form'

const SubmitButton = React.lazy(() => import('@/modules/form/components/actions/SubmitButton'))
const TextInput = React.lazy(() => import('@/modules/form/components/inputs/TextInput'))

export const { fieldContext, formContext, useFieldContext, useFormContext } = createFormHookContexts()
export const { useAppForm, withForm } = createFormHook({
fieldComponents: {
TextInput,
},
formComponents: {
SubmitButton,
},
fieldContext,
formContext,
})
import React from 'react'
import { createFormHook, createFormHookContexts } from '@tanstack/react-form'

const SubmitButton = React.lazy(() => import('@/modules/form/components/actions/SubmitButton'))
const TextInput = React.lazy(() => import('@/modules/form/components/inputs/TextInput'))

export const { fieldContext, formContext, useFieldContext, useFormContext } = createFormHookContexts()
export const { useAppForm, withForm } = createFormHook({
fieldComponents: {
TextInput,
},
formComponents: {
SubmitButton,
},
fieldContext,
formContext,
})
UserInfoForm.tsx :
import { z } from 'zod'
import { formOptions } from '@tanstack/react-form'
import { withForm } from '@/modules/form/form-context'

const userInfoSchema = z.object({
firstName: z.string().min(1, 'First name is required'),
lastName: z.string().min(1, 'Last name is required'),
})
export type UserInfoFormData = z.input<typeof userInfoSchema>

const userInfoFormOpt = formOptions({
defaultValues: {
firstName: '',
lastName: '',
} as UserInfoFormData,
validators: {
onChange: userInfoSchema,
},
})

const UserInfoForm = withForm({
...userInfoFormOpt,
props: {
title: 'User Information',
},
render: function Render({ form, title }) {
return (
<div>
<h2>{title}</h2>
<form.AppField name="firstName" children={(field) => <field.TextInput label="First Name" />} />
<form.AppField name="lastName" children={(field) => <field.TextInput label="Last Name" />} />
<form.AppField name="email" children={(field) => <field.TextInput label="Email" />} />
</div>
)
},
})

export default UserInfoForm
import { z } from 'zod'
import { formOptions } from '@tanstack/react-form'
import { withForm } from '@/modules/form/form-context'

const userInfoSchema = z.object({
firstName: z.string().min(1, 'First name is required'),
lastName: z.string().min(1, 'Last name is required'),
})
export type UserInfoFormData = z.input<typeof userInfoSchema>

const userInfoFormOpt = formOptions({
defaultValues: {
firstName: '',
lastName: '',
} as UserInfoFormData,
validators: {
onChange: userInfoSchema,
},
})

const UserInfoForm = withForm({
...userInfoFormOpt,
props: {
title: 'User Information',
},
render: function Render({ form, title }) {
return (
<div>
<h2>{title}</h2>
<form.AppField name="firstName" children={(field) => <field.TextInput label="First Name" />} />
<form.AppField name="lastName" children={(field) => <field.TextInput label="Last Name" />} />
<form.AppField name="email" children={(field) => <field.TextInput label="Email" />} />
</div>
)
},
})

export default UserInfoForm
AdressForm :
import { z } from 'zod'
import { formOptions } from '@tanstack/react-form'
import { withForm } from '@/modules/form/form-context'

const addressSchema = z.object({
street: z.string().min(1, 'Street is required'),
city: z.string().min(1, 'City is required'),
zipCode: z.string().min(1, 'Zip code is required'),
})

export type AddressFormData = z.input<typeof addressSchema>

const addressFormOpt = formOptions({
defaultValues: {
street: '',
city: '',
zipCode: '',
} as AddressFormData,
validators: {
onChange: addressSchema,
},
})

const AddressForm = withForm({
...addressFormOpt,
props: {
title: 'Address Information',
},
render: function Render({ form, title }) {
return (
<div>
<h2>{title}</h2>
<form.AppField name="street" children={(field) => <field.TextInput label="Street" />} />
<form.AppField name="city" children={(field) => <field.TextInput label="City" />} />
<form.AppField name="zipCode" children={(field) => <field.TextInput label="Zip Code" />} />
</div>
)
},
})

export default AddressForm
import { z } from 'zod'
import { formOptions } from '@tanstack/react-form'
import { withForm } from '@/modules/form/form-context'

const addressSchema = z.object({
street: z.string().min(1, 'Street is required'),
city: z.string().min(1, 'City is required'),
zipCode: z.string().min(1, 'Zip code is required'),
})

export type AddressFormData = z.input<typeof addressSchema>

const addressFormOpt = formOptions({
defaultValues: {
street: '',
city: '',
zipCode: '',
} as AddressFormData,
validators: {
onChange: addressSchema,
},
})

const AddressForm = withForm({
...addressFormOpt,
props: {
title: 'Address Information',
},
render: function Render({ form, title }) {
return (
<div>
<h2>{title}</h2>
<form.AppField name="street" children={(field) => <field.TextInput label="Street" />} />
<form.AppField name="city" children={(field) => <field.TextInput label="City" />} />
<form.AppField name="zipCode" children={(field) => <field.TextInput label="Zip Code" />} />
</div>
)
},
})

export default AddressForm
UserEditFormScreen.tsx
import UserInfoForm, { UserInfoFormData } from './UserInfoForm'
import AddressForm, { AddressFormData } from './AddressForm'
import { useAppForm } from '@/modules/form/form-context'

const UserProfileForm = () => {
const form = useAppForm({
defaultValues: {
firstName: '',
lastName: '',
email: '',
street: '',
city: '',
zipCode: '',
} as UserInfoFormData & AddressFormData,
onSubmit: ({ value }) => {
console.log(value)
},
})

return (
<form.AppForm>
<h1>User profile</h1>

{ // TypeScript error here }
<UserInfoForm form={form} title="User information" />

{ // TypeScript error here }
<AddressForm form={form} title="Address information" />

<form.AppForm>
<form.SubmitButton>Submit</form.SubmitButton>
</form.AppForm>
</form.AppForm>
)
}

export default UserProfileForm
import UserInfoForm, { UserInfoFormData } from './UserInfoForm'
import AddressForm, { AddressFormData } from './AddressForm'
import { useAppForm } from '@/modules/form/form-context'

const UserProfileForm = () => {
const form = useAppForm({
defaultValues: {
firstName: '',
lastName: '',
email: '',
street: '',
city: '',
zipCode: '',
} as UserInfoFormData & AddressFormData,
onSubmit: ({ value }) => {
console.log(value)
},
})

return (
<form.AppForm>
<h1>User profile</h1>

{ // TypeScript error here }
<UserInfoForm form={form} title="User information" />

{ // TypeScript error here }
<AddressForm form={form} title="Address information" />

<form.AppForm>
<form.SubmitButton>Submit</form.SubmitButton>
</form.AppForm>
</form.AppForm>
)
}

export default UserProfileForm
The error I encounter :
The expected type comes from property 'form' which is declared here on type 'IntrinsicAttributes & NoInfer<{ title: string; }> & { form: AppFieldExtendedReactFormApi<{ firstName: string; lastName: string; email: string; }, FormValidateOrFn<{ firstName: string; lastName: string; email: string; }> | undefined, ... 9 more ..., { ...; }>; } & { ...; }'
The expected type comes from property 'form' which is declared here on type 'IntrinsicAttributes & NoInfer<{ title: string; }> & { form: AppFieldExtendedReactFormApi<{ firstName: string; lastName: string; email: string; }, FormValidateOrFn<{ firstName: string; lastName: string; email: string; }> | undefined, ... 9 more ..., { ...; }>; } & { ...; }'
The only way I've found so far to avoid the TypeScript error is to add an as any to form.
return (
<UserInfoForm form={form as any} title="User information" />

<AddressForm form={form as any} title="Address information" />
)
return (
<UserInfoForm form={form as any} title="User information" />

<AddressForm form={form as any} title="Address information" />
)
ratty-blush
ratty-blush7mo ago
What version of TS are you using?
deep-jade
deep-jadeOP7mo ago
Currently version 5.8.2. This is my configuration:
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./src/*"
]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
ratty-blush
ratty-blush7mo ago
Strange. Can you make a minimal repro error and a bug issue on GH and I'll investigate?
deep-jade
deep-jadeOP7mo ago
Obviously, I'm preparing this very quickly. Thanks for your help !
ratty-blush
ratty-blush7mo ago
Ofc! Sorry you ran into this 😅
deep-jade
deep-jadeOP7mo ago
There's no need to apologise; it's understandable that not everything is perfect. The main thing is that we all work together in advance 😉. Here is the repository: https://github.com/philippe-desplats/tanstack-form-withform I have tried to do something that comes as close as possible to the environment of our project.
GitHub
GitHub - philippe-desplats/tanstack-form-withform
Contribute to philippe-desplats/tanstack-form-withform development by creating an account on GitHub.
deep-jade
deep-jadeOP7mo ago
I'll let you look, I'm going rest. See you soon and thank you for your help!
ratty-blush
ratty-blush7mo ago
You're awesome, thanks a bunch!
afraid-scarlet
afraid-scarlet7mo ago
is a repro/issue up yet? if not, I can help creating one. Fairly certain I‘ve run into this same problem before
ratty-blush
ratty-blush7mo ago
There'a repro from here: https://github.com/philippe-desplats/tanstack-form-withform But no GH issue yet - setting one up would be helpful! Thank you
GitHub
GitHub - philippe-desplats/tanstack-form-withform
Contribute to philippe-desplats/tanstack-form-withform development by creating an account on GitHub.
deep-jade
deep-jadeOP7mo ago
Hey! Sorry, I forgot to publish the issue on Tanstack Form's GitHub, my apologies! @Luca | LeCarbonator did you do it? Otherwise I'll take care of it!
afraid-scarlet
afraid-scarlet7mo ago
not yet! I was busy with some other stuff
deep-jade
deep-jadeOP7mo ago
It is in progress 😉 !
afraid-scarlet
afraid-scarlet7mo ago
glad to hear! let me know when you uploaded it
deep-jade
deep-jadeOP7mo ago
Here is the link to the GitHub Issue.
GitHub
Issues · TanStack/form
🤖 Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Lit. - Issues · TanStack/form
afraid-scarlet
afraid-scarlet7mo ago
well that was quick :Bueno:
yappiest-sapphire
yappiest-sapphire6mo ago
@Redek I had a simillar issue with ... is not assignable to type 'IntrinsicAttributes'., after updating typescript it went away
afraid-scarlet
afraid-scarlet6mo ago
strange. Did you perhaps forget to add the props argument to the render function?
deep-jade
deep-jadeOP6mo ago
Thank you for your feedback. Although I am using the latest version of TypeScript and TanStack Form, I still have the same problem. I have tried dozens of different codes, without solving the problem. It doesn't matter, I will test TanStack Form later. Thank you all for your help!
genetic-orange
genetic-orange5mo ago
Any updates on this? I think I’m running into the same issue as well on latest version. Trying to have two “subforms” created by the withForm hook and then using them in a useAppForm typescript is giving me errors because each withForm options is only a subset of the parent useAppForm options. Is this not possible?
genetic-orange
genetic-orange5mo ago
GitHub
withForm types break if defaultValues includes any extra field · I...
Describe the bug Using withForm breaks whenever any extra field is added to the parents defaultValues. import './App.css' import { useAppForm, withForm } from './hooks/form' functio...
genetic-orange
genetic-orange5mo ago
Similar more current issue, would be nice to see the team revisit this one.
rival-black
rival-black5mo ago
piggybacking on this, we should have a HOF wrapper like withForm but for non-components (like hooks and regular functions) since manually typing things from tsform is discouraged
ratty-blush
ratty-blush5mo ago
Interesting! What do you have in mind as an API? And where would it be used?
afraid-scarlet
afraid-scarlet5mo ago
someone brought up a wrapper that accepts a zod schema, generates defaultValues based on it, and then creates a useform with defaultValues and validators filled out. I forgot which post it was though
ratty-blush
ratty-blush5mo ago
This... Is actually an interesting idea 🕵️ But IDK how it'd fill out defaultValues for you
afraid-scarlet
afraid-scarlet5mo ago
GitHub
How to extend the useForm hook? · TanStack form · Discussion #1434
Hello, I'm new to the @tanstack/react-form library and I need help with customizing the useForm hook. What exactly do I want? Let's say I have a Zod schema for each of my forms: const Perso...
afraid-scarlet
afraid-scarlet5mo ago
custom implementation, just some function they called

Did you find this page helpful?