T
TanStackβ€’5mo ago
extended-salmon

Ways to create sub-form to be used as form array

I have ItemForm here
export const ItemForm = withForm({
defaultValues: {} as z.infer<typeof itemSchema>,
validators: {
onChange: itemSchema,
},
render: function Render({ form }) {
return (
<div
className={cn(
'col-span-6 items-center gap-2 rounded-xl border border-zinc-100 bg-white py-2.5',
'grid grid-cols-[2fr_5fr_2fr_2fr_2fr_0.5fr]'
)}
>
<form.Field name="type">
{(field) => (
<div className="flex flex-col gap-1">
<Select
value={field.state.value}
onValueChange={(
v: z.infer<typeof itemSchema>['type']
) => field.handleChange(v)}
>
<SelectTrigger>
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="form">Question</SelectItem>
<SelectItem value="notification">
Notification
</SelectItem>
</SelectContent>
</Select>

<FormMessage fieldMeta={field.state.meta} />
</div>
)}
</form.Field>

...
</div>
);
},
});
export const ItemForm = withForm({
defaultValues: {} as z.infer<typeof itemSchema>,
validators: {
onChange: itemSchema,
},
render: function Render({ form }) {
return (
<div
className={cn(
'col-span-6 items-center gap-2 rounded-xl border border-zinc-100 bg-white py-2.5',
'grid grid-cols-[2fr_5fr_2fr_2fr_2fr_0.5fr]'
)}
>
<form.Field name="type">
{(field) => (
<div className="flex flex-col gap-1">
<Select
value={field.state.value}
onValueChange={(
v: z.infer<typeof itemSchema>['type']
) => field.handleChange(v)}
>
<SelectTrigger>
<SelectValue placeholder="Type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="form">Question</SelectItem>
<SelectItem value="notification">
Notification
</SelectItem>
</SelectContent>
</Select>

<FormMessage fieldMeta={field.state.meta} />
</div>
)}
</form.Field>

...
</div>
);
},
});
No description
8 Replies
extended-salmon
extended-salmonOPβ€’5mo ago
And I want to create this ItemForm multiple time into array of form, but I also want to separate ItemForm form. Here's roughly how I do it but I got typescript error (in the image)
export const ProductFlowEventForm = withForm({
defaultValues: { events: [] } as z.infer<typeof schema>,
validators: {
onChange: schema,
},
render: function Render({ form }) {
return (
<div>
<form.Field name="events" mode="array">
{(field) => (
<div className="flex flex-col gap-2 rounded-2xl bg-background">
{field.state.value.map((_, i) => (
<ItemForm form={field.form} key={i} />
))}

<Button
className="col-span-4 flex w-full flex-row gap-2 text-[12px]"
variant="white"
type="button"
onClick={() =>
field.pushValue({
type: 'form',
})
}
>
<AddCircleIcon />
Add event
</Button>
</div>
)}
</form.Field>
</div>
);
},
});
export const ProductFlowEventForm = withForm({
defaultValues: { events: [] } as z.infer<typeof schema>,
validators: {
onChange: schema,
},
render: function Render({ form }) {
return (
<div>
<form.Field name="events" mode="array">
{(field) => (
<div className="flex flex-col gap-2 rounded-2xl bg-background">
{field.state.value.map((_, i) => (
<ItemForm form={field.form} key={i} />
))}

<Button
className="col-span-4 flex w-full flex-row gap-2 text-[12px]"
variant="white"
type="button"
onClick={() =>
field.pushValue({
type: 'form',
})
}
>
<AddCircleIcon />
Add event
</Button>
</div>
)}
</form.Field>
</div>
);
},
});
Schema are
export const itemSchema = z.object({
type: z.enum(['notification', 'form']),
});

export const schema = z.object({
events: z.array(itemSchema),
});
export const itemSchema = z.object({
type: z.enum(['notification', 'form']),
});

export const schema = z.object({
events: z.array(itemSchema),
});
extended-salmon
extended-salmonOPβ€’5mo ago
Here's minimal Stackblitz code.
unwilling-turquoise
unwilling-turquoiseβ€’5mo ago
the stackblitz is very helpful. I can fork it and adjust it to my approach if you'd like the main conflict is that your form doesn't actually extend properly. your ItemForm expects type to be set, but it's not
unwilling-turquoise
unwilling-turquoiseβ€’5mo ago
to summarize, you want to pass the index of the item's location into the item form component. That way, you can create a namespace of events[index] that gives you access to that subform's value as for passing the form from one withForm to another, you can just use the top-level form given as prop in it no need for field.form Also note this handy trick for typescript:
const item = `events[${itemIndex}]`; // item: string
const item = `events[${itemIndex}]` as const; // item: `events[${number}]`
const item = `events[${itemIndex}]`; // item: string
const item = `events[${itemIndex}]` as const; // item: `events[${number}]`
Let me know if you have more questions!
extended-salmon
extended-salmonOPβ€’5mo ago
Oh thank you for much ^_^ πŸ™†β€β™‚οΈ πŸ™‡β€β™‚οΈ So only way to use form composition is by have common fields right?, but nested form composition isn't possible?
unwilling-turquoise
unwilling-turquoiseβ€’5mo ago
no, youβ€˜d need a whole new Api for that. Otherwise, calling setFieldValue would have to be able to know however, if itβ€˜s guaranteed that you have the whole object, you can just handle state management on object level
extended-salmon
extended-salmonOPβ€’5mo ago
Thank you <3 πŸ™‡β€β™‚οΈ πŸ™‡β€β™‚οΈ

Did you find this page helpful?