From 945f8461cae43b6069c57fd9c06c26025db91372 Mon Sep 17 00:00:00 2001 From: drewsephski <147668908+drewsephski@users.noreply.github.com> Date: Thu, 8 Oct 2026 00:42:36 -0500 Subject: [PATCH] fix(form-core): set isValidating on every async validation run --- .changeset/quiet-fields-revalidate.md | 5 +++ packages/form-core/src/FieldApi.ts | 1 + packages/form-core/tests/FieldApi.spec.ts | 40 +++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 .changeset/quiet-fields-revalidate.md diff --git a/.changeset/quiet-fields-revalidate.md b/.changeset/quiet-fields-revalidate.md new file mode 100644 index 0000000000..cc0ea4fe60 --- /dev/null +++ b/.changeset/quiet-fields-revalidate.md @@ -0,0 +1,5 @@ +--- +'@tanstack/form-core': patch +--- + +Set `isValidating` on every async field validation run, not only the first one. diff --git a/packages/form-core/src/FieldApi.ts b/packages/form-core/src/FieldApi.ts index 80770f8897..a57ebca45d 100644 --- a/packages/form-core/src/FieldApi.ts +++ b/packages/form-core/src/FieldApi.ts @@ -1513,6 +1513,7 @@ export class FieldApi< field.timeoutIds.validations[validateObj.cause] = setTimeout( async () => { + field.timeoutIds.validations[validateObj.cause] = null if (controller.signal.aborted) return rawResolve(undefined) try { rawResolve( diff --git a/packages/form-core/tests/FieldApi.spec.ts b/packages/form-core/tests/FieldApi.spec.ts index 7c9a468ab8..ee517aa8b4 100644 --- a/packages/form-core/tests/FieldApi.spec.ts +++ b/packages/form-core/tests/FieldApi.spec.ts @@ -892,6 +892,46 @@ describe('field api', () => { storeunsub() }) + it('should set isValidating on every async validation run', async () => { + // Test for https://github.com/TanStack/form/issues/2372 + vi.useFakeTimers() + + const form = new FormApi({ + defaultValues: { + name: '', + }, + }) + + form.mount() + + const field = new FieldApi({ + form, + name: 'name', + validators: { + onChangeAsync: async ({ value }) => { + await sleep(1000) + return value === 'admin' ? 'Username is already taken' : undefined + }, + }, + }) + + field.mount() + + field.setValue('admin') + await vi.advanceTimersByTimeAsync(0) + expect(field.getMeta().isValidating).toBe(true) + await vi.runAllTimersAsync() + expect(field.getMeta().isValidating).toBe(false) + expect(field.getMeta().errors).toStrictEqual(['Username is already taken']) + + field.setValue('asdf') + await vi.advanceTimersByTimeAsync(0) + expect(field.getMeta().isValidating).toBe(true) + await vi.runAllTimersAsync() + expect(field.getMeta().isValidating).toBe(false) + expect(field.getMeta().errors).toStrictEqual([]) + }) + it('should run async validation onChange', async () => { vi.useFakeTimers()