From 7f33ac6532daf89deb5c8487543a020479791cd6 Mon Sep 17 00:00:00 2001 From: Maks Pikov Date: Thu, 21 May 2026 22:12:02 +0000 Subject: [PATCH] fix(form-core): bump _arrayVersion when async defaultValues update array fields --- packages/form-core/src/FormApi.ts | 11 +++++++ packages/react-form/tests/useField.test.tsx | 36 +++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index c12a0cfca..f145d7547 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -1512,6 +1512,17 @@ export class FormApi< ) }) + if (shouldUpdateValues) { + const helper = metaHelper(this) + for (const fieldKey of Object.keys( + this.fieldInfo, + ) as DeepKeys[]) { + if (Array.isArray(this.getFieldValue(fieldKey))) { + helper.bumpArrayVersion(fieldKey) + } + } + } + formEventClient.emit('form-api', { id: this._formId, state: this.store.state, diff --git a/packages/react-form/tests/useField.test.tsx b/packages/react-form/tests/useField.test.tsx index ef618e6bf..f9aa30887 100644 --- a/packages/react-form/tests/useField.test.tsx +++ b/packages/react-form/tests/useField.test.tsx @@ -1526,6 +1526,42 @@ describe('useField', () => { expect(getByTestId('item-1')).toHaveTextContent('John') }) + it('should rerender array field when async defaultValues resolve', async () => { + // Regression test for https://github.com/TanStack/form/issues/2178 + // When async defaultValues arrive after initial render, array fields in + // mode="array" must re-render because _arrayVersion is used as the + // reactivity signal (not value length). + type Person = { name: string } + type FormData = { people: Person[] } + + function Comp({ defaultValues }: { defaultValues?: FormData }) { + const form = useForm({ defaultValues }) + + return ( + + {(field) => ( +
    + {(field.state.value ?? []).map((person, i) => ( +
  1. + {person.name} +
  2. + ))} +
+ )} +
+ ) + } + + const { getByTestId, rerender } = render() + expect(getByTestId('list').children).toHaveLength(0) + + rerender() + await waitFor(() => + expect(getByTestId('list').children).toHaveLength(1), + ) + expect(getByTestId('item-0')).toHaveTextContent('Alice') + }) + it('should handle defaultValue without setstate-in-render error', async () => { // Spy on console.error before rendering const consoleErrorSpy = vi.spyOn(console, 'error')