I have made a small codesandbox with reproducible bug here.
I have a form component where I am setting useForm hook with initial values. Form has an IncomeInfo and a component with array field as child components:
const { pageFormValues, setPageFormValues, setActiveStep } = usePageProvider();
const initialValues = pageFormValues ?? createInitialValues(income);
const [action, setAction] = useState<ActionStatus>(ActionStatus.IDLE);
const useFormMethods = useForm({
defaultValues: initialValues,
});
return (
<FormProvider {...useFormMethods}>
<form onSubmit={useFormMethods.handleSubmit(onSubmit)}>
<IncomeInfo />
<IncomeBaseTable initialValues={initialValues} />
IncomeBaseTable looks like this:
export const IncomeBaseTable = ({ initialValues }) => (
<TableWrapper heading={["Period", "Employer", "Total", ""]}>
<ArrayField
name="incomeBase"
children={({ index, handleRemove, handleAppend }) => (
<TableRowWrapper
cells={[
<div className="flex gap-x-4">
<FormControlledDatePicker
key={`incomeBase[${index}].from`}
name={`incomeBase[${index}].from`}
label="From"
placeholder="DD.MM.ÅÅÅÅ"
defaultValue={initialValues.incomeBase[index].from}
hideLabel
/>
<FormControlledDatePicker
key={`incomeBase[${index}].to`}
name={`incomeBase[${index}].to`}
label="To"
placeholder="DD.MM.ÅÅÅÅ"
defaultValue={initialValues.incomeBase[index].to}
hideLabel
/>
</div>,
<FormControlledTextField
key={`incomeBase[${index}].employer`}
name={`incomeBase[${index}].employer`}
label="Employer"
hideLabel
/>,
<FormControlledTextField
key={`incomeBase[${index}].total`}
name={`incomeBase[${index}].total`}
label="Total"
type="number"
hideLabel
/>,
<Button
key={`delete-button-${index}`}
onClick={() => handleRemove(index)}
icon={<Delete aria-hidden />}
variant="tertiary"
size="xsmall"
/>,
]}
/>
)}
/>
</TableWrapper>
);
TableWrapper looks like this:
export const TableWrapper = ({ heading, children }: { heading: string[]; children: ReactNode }) => (
<Table size="small">
<Table.Header>
<Table.Row>
{heading.map((header) => (
<Table.HeaderCell scope="col" key={header}>
{header}
</Table.HeaderCell>
))}
</Table.Row>
</Table.Header>
<Table.Body>{children}</Table.Body>
</Table>
);
export const TableRowWrapper = ({ cells }: { cells: ReactElement[] }) => (
<Table.Row>
{cells.map((cell, index) => {
if (!index)
return (
<Table.HeaderCell scope="row" key={cell.key}>
{cell}
</Table.HeaderCell>
);
return <Table.DataCell key={index}>{cell}</Table.DataCell>;
})}
</Table.Row>
);
When I am appending from IncomeInfo to the field array like this, values get updated, but fields are the same as they were before the append action:
const { control, getValues } = useFormContext();
const { append, remove } = useFieldArray({
control,
name: "incomeBase",
});
const handleOnChange = (checked, incomeValues) => {
if (checked) {
append({
from: null,
to: null,
employer: "",
total: incomeValues.sum,
});
}
};
When I am logging in the ArrayFields component I can see that I get new values with getValues method, but fields are not updated:
const ArrayFields = ({ name, children }: ArrayFieldsProps) => {
const { control, getValues } = useFormContext();
const { fields, append, remove } = useFieldArray({
control,
name: name,
});
const handleAppend = (value) => {
append(value);
console.log("add: ", getValues());
};
const handleRemove = (index) => {
remove(index);
console.log("remove: ", getValues());
};
console.log("field", fields);
console.log("value", getValues().incomeBase);
Since fields are not updated the new field is not rendered. Why are values updated, but not fields?
If I append to the array field from IncomeBaseTable component, then it appends and rerenders with new fields:
export const IncomeBaseTable = ({ initialValues }) => (
<TableWrapper heading={["Period", "Employer", "Total", ""]}>
<ArrayField
name="incomeBase"
children={({ index, handleRemove, handleAppend }) => (
<TableRowWrapper
cells={[
<div className="flex gap-x-4">
<FormControlledDatePicker
key={`incomeBase[${index}].from`}
name={`incomeBase[${index}].from`}
label="From"
placeholder="DD.MM.ÅÅÅÅ"
defaultValue={initialValues.incomeBase[index].from}
hideLabel
/>
<FormControlledDatePicker
key={`incomeBase[${index}].to`}
name={`incomeBase[${index}].to`}
label="To"
placeholder="DD.MM.ÅÅÅÅ"
defaultValue={initialValues.incomeBase[index].to}
hideLabel
/>
</div>,
<FormControlledTextField
key={`incomeBase[${index}].employer`}
name={`incomeBase[${index}].employer`}
label="Employer"
hideLabel
/>,
<FormControlledTextField
key={`incomeBase[${index}].total`}
name={`incomeBase[${index}].total`}
label="Total"
type="number"
hideLabel
/>,
<Button
key={`add-button-${index}`}
onClick={() => handleAppend(fieldInitialValues)}
icon={<Add aria-hidden />}
variant="tertiary"
size="xsmall"
/>,
]}
/>
)}
/>
</TableWrapper>
);
Why is it adding to field array and rerendering when I append from the IncomeBaseTable, but not when I append from IncomeInfo component?
UPDATE
I managed to get it to work by using one instance of the useArrayField in the parent component and then passing it as a prop to child components. It won't work when I have more instances with same field name. Not sure why is that, beats the purpose of being a hook if I need to pass it around as a prop.