diff --git a/.changeset/fix-formgroup-distribute-unmounted-field.md b/.changeset/fix-formgroup-distribute-unmounted-field.md new file mode 100644 index 000000000..6c1370ed6 --- /dev/null +++ b/.changeset/fix-formgroup-distribute-unmounted-field.md @@ -0,0 +1,5 @@ +--- +'@tanstack/form-core': patch +--- + +Fix `FormGroupApi.distributeFieldErrors` throwing `TypeError: Cannot read properties of undefined` when a group validator returns errors for a field that is not mounted yet diff --git a/packages/form-core/src/FormGroupApi.ts b/packages/form-core/src/FormGroupApi.ts index 48e4642f8..4790d5505 100644 --- a/packages/form-core/src/FormGroupApi.ts +++ b/packages/form-core/src/FormGroupApi.ts @@ -1750,7 +1750,7 @@ export class FormGroupApi< continue } - this.form.setFieldMeta(fullName as never, (prev) => ({ + this.form.setFieldMeta(fullName as never, (prev = defaultFieldMeta) => ({ ...prev, errorMap: { ...prev.errorMap, diff --git a/packages/form-core/tests/FormGroupApi.spec.ts b/packages/form-core/tests/FormGroupApi.spec.ts index cf8269dfa..02d684fc1 100644 --- a/packages/form-core/tests/FormGroupApi.spec.ts +++ b/packages/form-core/tests/FormGroupApi.spec.ts @@ -542,6 +542,49 @@ describe('form group api', () => { expect(step1Group.state.meta.isValid).toBe(false) }) + it('Should not throw when a group validator returns field errors for a field that is not mounted yet', () => { + const form = new FormApi({ + defaultValues: { + step1: { name: '', unmountedField: '' }, + step2: { name: 'test2' }, + }, + }) + + const step1Group = new FormGroupApi({ + name: 'step1', + form, + validators: { + onMount: () => { + return { + fields: { + unmountedField: 'Unmounted field is required', + }, + } + }, + }, + }) + + // Note: no FieldApi is ever created/mounted for `step1.unmountedField`, + // so it has no entry in `fieldMetaBase` when the group validates. + form.mount() + + expect(() => step1Group.mount()).not.toThrow() + + expect(step1Group.state.meta.isFieldsValid).toBe(false) + + // Once the field mounts, the previously-distributed error should + // surface on it. + const unmountedField = new FieldApi({ + name: 'step1.unmountedField', + form, + }) + unmountedField.mount() + + expect(unmountedField.state.meta.errorMap.onMount).toBe( + 'Unmounted field is required', + ) + }) + it('Should propagate manual function field errors from group validators to child fields', async () => { const onSubmit = vi.fn()