Skip to content

Commit 9f231a9

Browse files
erikras-gilfoyle-agenterikras-gilfoyle-agent
andauthored
Test: add regression tests for form.getFieldState data preservation after remove (#165) (#194)
Co-authored-by: erikras-gilfoyle-agent <gilfoyle-agent@erikras.com>
1 parent c365924 commit 9f231a9

File tree

1 file changed

+103
-0
lines changed

1 file changed

+103
-0
lines changed

src/FieldArray.test.tsx

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -966,4 +966,107 @@ describe('FieldArray', () => {
966966
expect('touched' in metaSnapshot).toBe(true)
967967
expect('valid' in metaSnapshot).toBe(true)
968968
})
969+
970+
it('should preserve data property in field state after remove', async () => {
971+
// Reproduces: https://github.com/final-form/react-final-form-arrays/issues/165
972+
// form.getFieldState is corrupted after arrays.remove - data is lost for shifted fields
973+
let formRef: any = null
974+
975+
const { getByText } = render(
976+
<Form
977+
onSubmit={onSubmitMock}
978+
mutators={{
979+
...arrayMutators,
980+
setFieldData: ([name, data]: [string, any], state: any) => {
981+
if (state.fields[name]) {
982+
state.fields[name].data = data
983+
}
984+
}
985+
}}
986+
initialValues={{ customers: ['Alice', 'Bob'] }}
987+
>
988+
{({ form }) => {
989+
formRef = form
990+
return (
991+
<form>
992+
<FieldArray name="customers">
993+
{({ fields }) =>
994+
fields.map((name, index) => (
995+
<div key={index}>
996+
<Field name={name} component="input" />
997+
<button
998+
type="button"
999+
onClick={() => fields.remove(index)}
1000+
>
1001+
Remove {index}
1002+
</button>
1003+
</div>
1004+
))
1005+
}
1006+
</FieldArray>
1007+
</form>
1008+
)
1009+
}}
1010+
</Form>
1011+
)
1012+
1013+
// Set data on both fields
1014+
act(() => {
1015+
formRef.mutators.setFieldData('customers[0]', { disabled: true })
1016+
formRef.mutators.setFieldData('customers[1]', { disabled: true })
1017+
})
1018+
1019+
// Remove element at index 0
1020+
act(() => {
1021+
fireEvent.click(getByText('Remove 0'))
1022+
})
1023+
1024+
// After removal, customers[1] becomes customers[0]
1025+
// Its data property must be preserved — not undefined or {}
1026+
const fieldState = formRef.getFieldState('customers[0]')
1027+
expect(fieldState).toBeDefined()
1028+
expect(fieldState!.data).toEqual({ disabled: true })
1029+
})
1030+
1031+
it('should return defined field state (not undefined) for shifted field immediately after remove', () => {
1032+
// Edge case: getFieldState should return the field state, not undefined,
1033+
// even when called synchronously after remove (before React re-renders).
1034+
// copyField sets lastFieldState: undefined to force re-notification,
1035+
// but getFieldState(name) returns field.lastFieldState — which would be undefined.
1036+
let formRef: any = null
1037+
let removeRef: any = null
1038+
1039+
render(
1040+
<Form
1041+
onSubmit={onSubmitMock}
1042+
mutators={arrayMutators}
1043+
initialValues={{ items: ['a', 'b'] }}
1044+
>
1045+
{({ form }) => {
1046+
formRef = form
1047+
return (
1048+
<form>
1049+
<FieldArray name="items">
1050+
{({ fields }) => {
1051+
removeRef = fields.remove
1052+
return fields.map((name, index) => (
1053+
<Field key={index} name={name} component="input" />
1054+
))
1055+
}}
1056+
</FieldArray>
1057+
</form>
1058+
)
1059+
}}
1060+
</Form>
1061+
)
1062+
1063+
act(() => {
1064+
removeRef(0)
1065+
})
1066+
1067+
// After remove + React re-render cycle, getFieldState should be defined
1068+
const fieldState = formRef.getFieldState('items[0]')
1069+
expect(fieldState).toBeDefined()
1070+
expect(fieldState!.value).toBe('b')
1071+
})
9691072
})

0 commit comments

Comments
 (0)