Skip to content

Commit 6b92c45

Browse files
test: cover positional name shifts with stable React keys
1 parent e23f8b5 commit 6b92c45

1 file changed

Lines changed: 75 additions & 0 deletions

File tree

src/useField.issue-1095.test.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,26 @@ import Field from "./Field";
77
const onSubmitMock = () => {};
88
const arrayInitialValues = { items: [{ name: "a" }, { name: "b" }] };
99
const nestedInitialValues = { parent: { child: { value: null } } };
10+
const listInitialValues = {
11+
items: [
12+
{ id: "a", label: "A" },
13+
{ id: "b", label: "B" },
14+
{ id: "c", label: "C" },
15+
],
16+
};
17+
18+
// Stable React key per item, positional field name per index. A row that moves
19+
// keeps its instance and only changes its `name`, so the registration effect
20+
// re-runs against a path final-form dropped during the same commit's cleanup.
21+
const renderRows = (values) =>
22+
values.items.map((item, index) => (
23+
<Field
24+
key={item.id}
25+
name={`items[${index}].label`}
26+
component="input"
27+
data-testid={`label-${item.id}`}
28+
/>
29+
));
1030

1131
describe("useField issue #1095", () => {
1232
it("does not overwrite a value set through change() when a field first mounts at that path", () => {
@@ -316,4 +336,59 @@ describe("useField issue #1095", () => {
316336
expect(getByTestId("late").value).toBe("seeded");
317337
expect(form.getState().values.late).toBe("seeded");
318338
});
339+
340+
it("keeps edited labels with their rows when an item is inserted at the head", () => {
341+
let form;
342+
const { getByTestId } = render(
343+
<Form onSubmit={onSubmitMock} initialValues={listInitialValues}>
344+
{(props) => {
345+
form = props.form;
346+
return renderRows(props.values);
347+
}}
348+
</Form>,
349+
);
350+
351+
act(() => form.change("items[0].label", "A-edited"));
352+
353+
act(() =>
354+
form.change("items", [
355+
{ id: "d", label: "D" },
356+
...form.getState().values.items,
357+
]),
358+
);
359+
360+
expect(form.getState().values.items).toEqual([
361+
{ id: "d", label: "D" },
362+
{ id: "a", label: "A-edited" },
363+
{ id: "b", label: "B" },
364+
{ id: "c", label: "C" },
365+
]);
366+
expect(getByTestId("label-d").value).toBe("D");
367+
expect(getByTestId("label-a").value).toBe("A-edited");
368+
});
369+
370+
it("keeps edited labels with their rows when an item is removed from the middle", () => {
371+
let form;
372+
const { getByTestId } = render(
373+
<Form onSubmit={onSubmitMock} initialValues={listInitialValues}>
374+
{(props) => {
375+
form = props.form;
376+
return renderRows(props.values);
377+
}}
378+
</Form>,
379+
);
380+
381+
act(() => form.change("items[2].label", "C-edited"));
382+
383+
act(() => {
384+
const items = form.getState().values.items;
385+
form.change("items", [items[0], items[2]]);
386+
});
387+
388+
expect(form.getState().values.items).toEqual([
389+
{ id: "a", label: "A" },
390+
{ id: "c", label: "C-edited" },
391+
]);
392+
expect(getByTestId("label-c").value).toBe("C-edited");
393+
});
319394
});

0 commit comments

Comments
 (0)