Closed platosha closed 3 months ago
Considering the outcome of the #1186, I believe we could proceed with the first option (with changes):
function OrderFormRow({ model: row }) {
const { field, removeSelf } = useFormPart(row);
return (
<li>
<TextField label="Product" {...field(row)}/>
<button onClick={removeSelf}>Remove</button>
</li>
);
}
function OrderForm() {
const { model, field, appendItem } = useForm(OrderForm);
return <ul>
{
model.rows.map((row) => (
<OrderFormRow model={row} key={row.key} />
))
}
<button onClick={appendItem}>Add</button>
</ul>
}
How would the appendItem
helper in the OrderForm
know that it refers to model.rows
?
After a fresh look, useFormArray
/ .map
combination looks simpler.
useFormArray
could return append
, remove
, and the map
function that iterates through the items and provides itemModel
and key
.
function OrderForm() {
const {model, field, access} = useForm(OrderFormModel);
const {append, map, remove} = useFormArray(model.rows);
return <ul>
{
map((item, index) => (
<li key={item.key}>
<TextField label="Product" {...field(item.model.product)}/>
<button onClick={item.remove}>Remove</button>
</li>
))
}
<button onClick={append}>Add</button>
</ul>
}
We discussed the options and concluded that we'd introduce a separate useFormArrayPart
hook:
function TeamFormPlayer({ model }: { model: PlayerModel }) {
const { field, value, invalid, ownErrors } = useFormPart(model);
return (
<div>
<input data-testid={`lastName.${value!.id}`} type="text" {...field(model.lastName)} />
<output data-testid={`validation.lastName.${value!.id}`}>
{invalid ? ownErrors.map((e) => e.message).join(', ') : 'OK'}
</output>
</div>
);
}
function TeamForm() {
const { field, model } = useForm(TeamModel);
const { items, setValue: setPlayers, value: playersValue } = useFormArrayPart(model.players);
const name = useFormPart(model.name);
return (
<>
<input data-testid="team.name" type="text" {...field(model.name)} />
<output data-testid="validation.team.name">
{name.invalid ? name.ownErrors.map((e) => e.message).join(', ') : 'OK'}
</output>
{items.map((playerModel, index) => (
<TeamFormPlayer key={`${playersValue[index].id ?? -index}`} model={playerModel} />
))}
</>
);
}
👍
Let's discuss and figure out the API of using arrays in React forms. Consider the following use cases:
value
,errors
, and such)key
for rendering in ReactOne simple way items is to add the
.map
method directly to theArrayModel
that maps the item models using the provided callback, similar toArray.prototype.map
. However, since we share models between Lit and React binders, the model API should preferably stay free of Lit and React opinionated helpers. This would mean that, as a baseline, the mapping callback provides the only themodel
for the rendering:This alone is clearly not sufficient for the use cases. Based on the
access
idea from https://github.com/vaadin/hilla/issues/1186, we could potentially add thekey
and the array mutation helpers to theaccess
result:Or, alternatively, we could add another hook for the array use case: