From 97e8183fffa93ba65209c5f5067ec41a95ed2443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E8=89=B3=E5=85=B5?= Date: Mon, 29 Dec 2025 18:43:20 +0800 Subject: [PATCH 1/3] fix(Form.List): do not skip list root when no child field entities exist --- src/hooks/useForm.ts | 17 +++++++++++++++-- tests/list.test.tsx | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/hooks/useForm.ts b/src/hooks/useForm.ts index 3353d8e61..620b6d4d2 100644 --- a/src/hooks/useForm.ts +++ b/src/hooks/useForm.ts @@ -293,8 +293,21 @@ export class FormStore { // Ignore when it's a list item and not specific the namePath, // since parent field is already take in count if ((entity as FieldEntity).isList?.()) { - listNamePaths.push(namePath); - return; + // 这里直接跳过跟节点是有bug的,getFieldsValue("list") 会返回空对象 + const hasChild = fieldEntities.some(other => { + if (other === entity) return false; + const otherNamePath = other.INVALIDATE_NAME_PATH || other.getNamePath(); + return ( + otherNamePath.length > namePath.length && + matchNamePath(otherNamePath as InternalNamePath, namePath as InternalNamePath, true) + ); + }); + + // 只有当它确实有子节点时,才跳过根节点 + if (hasChild) { + listNamePaths.push(namePath); + return; + } } if (!mergedFilterFunc) { diff --git a/tests/list.test.tsx b/tests/list.test.tsx index 8a207e240..5a1f7d6c4 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -1139,4 +1139,31 @@ describe('Form.List', () => { { list: [{ name: 'John', tags: ['react', 'ts', 'redux'] }] }, ); }); + + it('getFieldsValue("list") should not be empty object when list has value but no child Field entities', async () => { + const [container] = generateForm( + () => ( + { + form.current!.setFieldValue(['list', 0, 'name'], (e.target as HTMLInputElement).value); + }} + /> + ), + { + initialValues: { + list: [{ name: '123' }], + }, + }, + ); + + expect(form.current!.getFieldsValue(['list'])).toEqual({ list: [{ name: '123' }] }); + + await act(async () => { + fireEvent.change(container.querySelector('input.raw-name')!, { target: { value: '456' } }); + }); + + expect(form.current!.getFieldsValue(['list'])).toEqual({ list: [{ name: '456' }] }); + }); }); From e162800b4702d5a914f5248fa3a837fba3d28352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E8=89=B3=E5=85=B5?= Date: Tue, 30 Dec 2025 11:25:39 +0800 Subject: [PATCH 2/3] test: add test for getFieldsValue(["list"]) --- tests/list.test.tsx | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/list.test.tsx b/tests/list.test.tsx index f3dba0ad0..2871ed0b9 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -1203,4 +1203,31 @@ describe('Form.List', () => { list: [{ name: 'A' }, { name: 'BB' }, { name: 'C' }, { name: 'D' }], }); }); + + it('getFieldsValue(["list"]) should keep list value when list has value but no child Field entities', async () => { + const [container] = generateForm( + () => ( + { + form.current!.setFieldValue(['list', 0, 'name'], (e.target as HTMLInputElement).value); + }} + /> + ), + { + initialValues: { + list: [{ name: '123' }], + }, + }, + ); + + expect(form.current!.getFieldsValue(['list'])).toEqual({ list: [{ name: '123' }] }); + + await act(async () => { + fireEvent.change(container.querySelector('input.raw-name')!, { target: { value: '456' } }); + }); + + expect(form.current!.getFieldsValue(['list'])).toEqual({ list: [{ name: '456' }] }); + }); }); From 83700f6c86e1d682eeb097e8f6eafe0168d4fb46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E8=89=B3=E5=85=B5?= Date: Tue, 30 Dec 2025 11:50:03 +0800 Subject: [PATCH 3/3] test: remove duplicated test cases merged from master --- tests/list.test.tsx | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/tests/list.test.tsx b/tests/list.test.tsx index 2871ed0b9..521f23288 100644 --- a/tests/list.test.tsx +++ b/tests/list.test.tsx @@ -1172,38 +1172,6 @@ describe('Form.List', () => { }); }); - it('should not drop list items when updating list item field', async () => { - const onValuesChange = jest.fn(); - - const { getAllByRole } = render( -
- - {fields => - fields.map(field => ( - - - - )) - } - -
, - ); - - // Change second item (index = 1) - fireEvent.change(getAllByRole('textbox')[1], { - target: { value: 'BB' }, - }); - - const [, allValues] = onValuesChange.mock.calls.pop(); - - expect(allValues).toEqual({ - list: [{ name: 'A' }, { name: 'BB' }, { name: 'C' }, { name: 'D' }], - }); - }); - it('getFieldsValue(["list"]) should keep list value when list has value but no child Field entities', async () => { const [container] = generateForm( () => (