-
I'm trying to implement a pattern of reusable sections of the form. type AddressSectionFormValues = {
streetName?: string
city?: string
country?: string
} I want to use a single component called Example parent form (PersonForm is an example, it could be other parents like OrderForm, etc): type PersonFormValues = {fullName?: string} & AddressSectionFormValues
const PersonForm = () => {
const form = useForm<PersonFormValues>()
return (
<div>
<input {...register("fullName")} />
<AddressSection form={form} /> /* <-- Trying to use the generic section here */
</div>
)
} To build out the AddressSection types, I wanted something that would guarantee I'd have AT LEAST the fields in AddressSectionFormValues + ANYTHING else (the parent fields). Here are my attempts: 1. Join a generic with the known type.const AddressSection = <T,>({ form }: { form: UseFormReturn<AddressSectionFormValues & T> }) => { ... } Doesn't work, when using one of the fields from AddressSection type, it errors out, e.g.: 2. Join known types with a catch all Record<string, unknown>const AddressSection = ({ form }: { form: UseFormReturn<AddressSectionFormValues & Record<string, unknown> }) => { ... } Works for the AddressSection, no type errors there, but the parent is unable to use it, because of a typescript limitation where: {
someKnownProperty?: number
[key: string]?: string
} becomes { [key: string]?: string } Because UseFormReturn<AddressSectionFormValues & Record<string & not keyof AddressSectionFormValues, unknown> 3. Use any and type assertions:const AddressSection = ({ form }: { form: UseFormReturn<any>}) => {
const { register, watch, ...otherMethods} = form as UseFormReturn<AddressSectionFormValues>
} This works and it's what I'm using, but it's not type-safe :( You could pass any form to AddressSection and it would accept it, even without the necessary keys. Is there any way I could reuse the forms sections with type-safety? This discussion from @felixschorer #7354 seems to be working towards a similar goal but it's been stale, and I'm not sure how I could use it in v7. |
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
I was able to achieve a good level of typesafety on this, and I'm using this from now on until we have a better alternative. export const narrowForm = <TFieldValues extends FieldValues = FieldValues, TContext = any>(
form: any,
) => form as unknown as UseFormReturn<TFieldValues, TContext> And you'd define the section as: // Define the fields that the section will contain
type ExampleFormValues = {
name: string
age: number
}
const ExampleSection = <T extends FieldValues>({
form,
}: {
form: UseFormReturn<T & ExampleFormValues>
}) => {
const { register } = narrowForm<ExampleFormValues>(form)
return (
<div>
<input {...register("name")} />
<input {...register("age")} />
</div>
)
} it will basically force the form to narrow it's type to the section type. Now when you use type ParentFormValues = {
anotherProperty: string
} & ExampleFormValues
const ParentForm = () => {
const { register, handleSubmit } = useForm<ParentFormValues>()
return (
<div>
<form onSubmit={handleSubmit(data => console.log(data))}>
<input {...register("anotherProperty")} />
<ExampleSection form={form} />
</form>
</div>
)
} If |
Beta Was this translation helpful? Give feedback.
-
I was able to do it without any assertions |
Beta Was this translation helpful? Give feedback.
I was able to achieve a good level of typesafety on this, and I'm using this from now on until we have a better alternative.
The main magic is the following utility function:
And you'd define the section as: