diff --git a/.changeset/shy-otters-give.md b/.changeset/shy-otters-give.md new file mode 100644 index 000000000..a558da6a7 --- /dev/null +++ b/.changeset/shy-otters-give.md @@ -0,0 +1,5 @@ +--- +'@tanstack/form-core': patch +--- + +fix: create fieldMeta on async validation for fields with errors diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index f1b5abde4..c77499fda 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -1879,16 +1879,20 @@ export class FormApi< } const errorMapKey = getErrorMapKey(validateObj.cause) - for (const field of Object.keys( - this.state.fieldMeta, - ) as DeepKeys[]) { - if (this.baseStore.state.fieldMetaBase[field] === undefined) { + const allFieldsToProcess = new Set([ + ...Object.keys(this.state.fieldMeta), + ...Object.keys(fieldErrorsFromFormValidators || {}), + ] as DeepKeys[]) + + for (const field of allFieldsToProcess) { + if ( + this.baseStore.state.fieldMetaBase[field] === undefined && + !fieldErrorsFromFormValidators?.[field] + ) { continue } - const fieldMeta = this.getFieldMeta(field) - if (!fieldMeta) continue - + const fieldMeta = this.getFieldMeta(field) ?? defaultFieldMeta const { errorMap: currentErrorMap, errorSourceMap: currentErrorMapSource, @@ -1910,7 +1914,7 @@ export class FormApi< // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition currentErrorMap?.[errorMapKey] !== newErrorValue ) { - this.setFieldMeta(field, (prev) => ({ + this.setFieldMeta(field, (prev = defaultFieldMeta) => ({ ...prev, errorMap: { ...prev.errorMap, diff --git a/packages/form-core/tests/FormApi.spec.ts b/packages/form-core/tests/FormApi.spec.ts index c1b36a85c..3940fa67a 100644 --- a/packages/form-core/tests/FormApi.spec.ts +++ b/packages/form-core/tests/FormApi.spec.ts @@ -1873,6 +1873,37 @@ describe('form api', () => { expect(formSubmit).toHaveBeenCalledOnce() }) + it('should create field meta from form async submit validation errors', async () => { + vi.useFakeTimers() + + const form = new FormApi({ + defaultValues: { + age: 0, + }, + validators: { + onSubmitAsync: async ({ value }) => { + await sleep(1) + return value.age > 0 + ? undefined + : { fields: { age: 'age must be greater than 0' } } + }, + }, + asyncDebounceMs: 1, + }) + + form.mount() + + expect(form.state.fieldMeta.age).toBeUndefined() + + form.handleSubmit() + await vi.runAllTimersAsync() + + expect(form.state.fieldMeta.age).toBeDefined() + expect(form.state.fieldMeta.age?.errorMap.onSubmit).toBe( + 'age must be greater than 0', + ) + }) + it('should run all types of async validation on fields during submit', async () => { vi.useFakeTimers()