T
TanStack8mo ago
unwilling-turquoise

Array of Forms

What is the best approach to have a list of forms with multiple fields? Use Case: List of task cards with select box and editable content. User can modify content and toggle select, then use submit to add all tasks. I tried following the example for array fields, but not sure how to relate to form arrays. Should TaskCard accept form directly? How to properly tell TaskCard which item in task array? <form.Field name="tasks" mode="array"> {(field) => { return ( <> {field.state.value.map((task, i) => { return ( <Grid2 key={i}> <TaskCard form={form} /> </Grid2> ); })} </> ); }} </form.Field>
7 Replies
other-emerald
other-emerald8mo ago
Is the task card supposed to be a single CRUD operation? as in when you save do you save you save the single card you were editing? or do you save all of the cards at the same time? To me, it looks like your sat in between re-usable components, and a field array... It should be noted that the docs for re-usable components isn't finalised yet.... Which I think your confusion is stemming from. If you were to do it as an array you could do it like this... Where you have the form.Fields at the level of your form, otherwise I would take a look at https://github.com/TanStack/form/pull/825 for reusable components (which is what I think your trying to do in your example), but it's still being finalised. (probably wont work, quickly thrown together)
<Field name="tasks" mode="array">
{(field) => {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{field.state.value.map((_, i) => {
return (
<div>
<Field
key={i}
name={`tasks[${i}].active`}
>
{(subField) => {
return (
<Radio
state={subField.state}
handleChange={subField.handleChange}
/>
);
}}
</Field>

<Field key={i} name={`tasks[${i}].description`}>
{(subField) => (
<TextField
state={subField.state}
handleChange={subField.handleChange}
/>
)}
</Field>
</div>
);
})}
</div>
);
}}
</Field>
<Field name="tasks" mode="array">
{(field) => {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '10px' }}>
{field.state.value.map((_, i) => {
return (
<div>
<Field
key={i}
name={`tasks[${i}].active`}
>
{(subField) => {
return (
<Radio
state={subField.state}
handleChange={subField.handleChange}
/>
);
}}
</Field>

<Field key={i} name={`tasks[${i}].description`}>
{(subField) => (
<TextField
state={subField.state}
handleChange={subField.handleChange}
/>
)}
</Field>
</div>
);
})}
</div>
);
}}
</Field>
GitHub
[WIP] Shared Component Library Docs by crutchcorn · Pull Request #8...
This PR: Adds an example of moving TanStack Form into your own shared component library Adds the docs to explain what the example is doing
unwilling-turquoise
unwilling-turquoiseOP8mo ago
Oh, awesome. Thanks for pointing out that PR. The idea was to pre-populate a list of suggested tasks, allow the user to select/modify tasks, then bulk add all selected. Since I also would like the component to be in a different file, it may be too much with arrays at the current state of the package. I think for now, I will try keep the card content static, but instead just wrap with a select button using arrays. I was still having issues with this approach but I'll try some more and follow up if needed. Thank you
other-emerald
other-emerald8mo ago
yeah, it sounds like an array field... I would wait for that pr to get merged if you want to make them their own re-usable components. I've tried drafting a PR for docs relating passing form as a prop, and the Type inference become nigh impossible to match, so you lose a lot of Ts utility. Though the context option they’re talking about in that PR, will solve a lot of the issues.
unwilling-turquoise
unwilling-turquoiseOP8mo ago
To close the loop a bit, here's what I ended up doing. This avoids the use of re-usable form components and just uses an array of select boxes for each card
{sectionTasks.map((task) => (
<Grid2
key={task.index}
container
alignItems="center"
sx={{ display: "flex", alignItems: "center", width: 1 }}
>
<Grid2 size={2}>
<form.Field
name={`selectedTasksIx[${task.index}]`}
children={({ state, handleChange, handleBlur }) => (
<Checkbox
checked={state.value}
onChange={(e) => handleChange(e.target.checked)}
onBlur={handleBlur}
/>
)}
/>
</Grid2>

{/* Task Card */}
<Grid2 size={10}>
<TaskCard task={task} />
</Grid2>
</Grid2>
))}
{sectionTasks.map((task) => (
<Grid2
key={task.index}
container
alignItems="center"
sx={{ display: "flex", alignItems: "center", width: 1 }}
>
<Grid2 size={2}>
<form.Field
name={`selectedTasksIx[${task.index}]`}
children={({ state, handleChange, handleBlur }) => (
<Checkbox
checked={state.value}
onChange={(e) => handleChange(e.target.checked)}
onBlur={handleBlur}
/>
)}
/>
</Grid2>

{/* Task Card */}
<Grid2 size={10}>
<TaskCard task={task} />
</Grid2>
</Grid2>
))}
other-emerald
other-emerald8mo ago
nice one, yeah I'm doing something similar.
unwilling-turquoise
unwilling-turquoiseOP8mo ago
One thing i'm struggling with is toggling the select all button when changes are made to the individual
other-emerald
other-emerald8mo ago
have you looked into the field.listeners api? might be what your looking for

Did you find this page helpful?