From 9a49d4585d7e68ea34534541cd2018bf81159ae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 11:40:22 +0800 Subject: [PATCH 1/3] test: test driven --- tests/list.test.tsx | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/list.test.tsx b/tests/list.test.tsx index e7670d84..3b3306f5 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -1042,4 +1042,37 @@ describe('Form.List', () => { expect(onFinishFailed).toHaveBeenCalled(); }); + + it('List should have correct onValuesChange', () => { + const onValuesChange = jest.fn(); + + const [container] = generateForm( + fields => ( +
+ {fields.map(field => ( +
+ + + + + + +
+ ))} +
+ ), + { + initialValues: { + list: [{ first: 'light' }], + }, + onValuesChange, + }, + ); + + fireEvent.change(getInput(container, 1), { target: { value: 'little' } }); + expect(onValuesChange).toHaveBeenCalledWith( + { list: [{ last: 'little' }] }, + { list: [{ first: 'light', last: 'little' }] }, + ); + }); }); From e226ffea8774304eecaa4d0121fd52157f6bcf93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 17:30:29 +0800 Subject: [PATCH 2/3] chore: update values --- package.json | 2 +- src/useForm.ts | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 30df0953..482c1107 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ }, "dependencies": { "@rc-component/async-validator": "^5.0.3", - "@rc-component/util": "^1.3.0", + "@rc-component/util": "^1.5.0", "clsx": "^2.1.1" }, "devDependencies": { diff --git a/src/useForm.ts b/src/useForm.ts index 5799eec1..5d4d07e8 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -1,4 +1,5 @@ import { merge } from '@rc-component/util/lib/utils/set'; +import { mergeWith } from '@rc-component/util'; import warning from '@rc-component/util/lib/warning'; import * as React from 'react'; import { HOOK_MARK } from './FieldContext'; @@ -786,7 +787,10 @@ export class FormStore { const changedValues = cloneByNamePathList(this.store, [namePath]); const allValues = this.getFieldsValue(); // Merge changedValues into allValues to ensure allValues contains the latest changes - const mergedAllValues = merge(allValues, changedValues); + const mergedAllValues = mergeWith([allValues, changedValues], { + // When value is array, it means trigger by Form.List which should replace directly + prepareArray: current => (Array.isArray(value) ? [] : [...(current || [])]), + }); onValuesChange(changedValues, mergedAllValues); } From abde56caf9999d5079fb74c830e053d5d1a790ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Mon, 8 Dec 2025 18:05:29 +0800 Subject: [PATCH 3/3] fix: list check logic --- src/useForm.ts | 3 ++- tests/list.test.tsx | 64 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/useForm.ts b/src/useForm.ts index 5d4d07e8..e2cd4b70 100644 --- a/src/useForm.ts +++ b/src/useForm.ts @@ -784,12 +784,13 @@ export class FormStore { const { onValuesChange } = this.callbacks; if (onValuesChange) { + const fieldEntity = this.getFieldsMap(true).get(namePath); const changedValues = cloneByNamePathList(this.store, [namePath]); const allValues = this.getFieldsValue(); // Merge changedValues into allValues to ensure allValues contains the latest changes const mergedAllValues = mergeWith([allValues, changedValues], { // When value is array, it means trigger by Form.List which should replace directly - prepareArray: current => (Array.isArray(value) ? [] : [...(current || [])]), + prepareArray: current => (fieldEntity?.isList() ? [] : [...(current || [])]), }); onValuesChange(changedValues, mergedAllValues); } diff --git a/tests/list.test.tsx b/tests/list.test.tsx index 3b3306f5..8a207e24 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -1075,4 +1075,68 @@ describe('Form.List', () => { { list: [{ first: 'light', last: 'little' }] }, ); }); + + it('should correctly merge array-valued fields within Form.List items without losing data', async () => { + const TagInput = ({ value = [], onChange }: any) => ( + { + const newValue = e.target.value + .split(',') + .map(s => s.trim()) + .filter(Boolean); + onChange(newValue); + }} + /> + ); + + const onValuesChange = jest.fn(); + + const [container] = generateForm( + fields => ( + <> + {fields.map(field => ( +
+ + + + + + +
+ ))} + + ), + { + initialValues: { + list: [{ name: 'John', tags: ['react', 'js'] }], + }, + onValuesChange, + }, + ); + + const tagInput = container.querySelector('input[data-testid="tag-input"]') as HTMLElement; + + await act(async () => { + fireEvent.change(tagInput, { + target: { value: 'react,ts' }, + }); + }); + + expect(onValuesChange).toHaveBeenCalledWith( + { list: [{ tags: ['react', 'ts'] }] }, // changedValues + { list: [{ name: 'John', tags: ['react', 'ts'] }] }, // allValues + ); + onValuesChange.mockReset(); + + await act(async () => { + fireEvent.change(tagInput, { target: { value: 'react,ts,redux' } }); + }); + + expect(onValuesChange).toHaveBeenLastCalledWith( + { list: [{ tags: ['react', 'ts', 'redux'] }] }, + { list: [{ name: 'John', tags: ['react', 'ts', 'redux'] }] }, + ); + }); });