From 01ee70b1d20b914f76bd8a015943f1d67886da6b Mon Sep 17 00:00:00 2001 From: easeurmind <64529155+restareaByWeezy@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:18:18 +0900 Subject: [PATCH 1/2] fix(form-core): run field validators on re-submission to clear stale errors When a field has an onBlur validator that produces an error, subsequent form submission attempts are blocked by a canSubmit check that runs before validateAllFields. This means stale onBlur errors (e.g. from a field that is no longer invalid) prevent re-submission. Fix: on re-submission attempts (submissionAttempts > 1), skip the early canSubmit return so validateAllFields can re-run all field validators for the submit cause. Stale errors are cleared and the isFieldsValid check below handles the invalidity gate correctly. First-submission behavior (submissionAttempts <= 1) is unchanged so existing canSubmit semantics (e.g. onMount errors) are preserved. Fixes #2034 --- packages/form-core/src/FormApi.ts | 19 +++++++--- packages/form-core/tests/FormApi.spec.ts | 47 ++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 6 deletions(-) diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index 2317fb2b9..a7e4fdaae 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -2105,12 +2105,19 @@ export class FormApi< submitMeta ?? (this.options.onSubmitMeta as TSubmitMeta) if (!this.state.canSubmit && !this._devtoolsSubmissionOverride) { - this.options.onSubmitInvalid?.({ - value: this.state.values, - formApi: this, - meta: submitMetaArg, - }) - return + // On re-submission (submissionAttempts > 1), skip the early return so + // validateAllFields can re-run and clear stale field errors (e.g. from a + // previous onBlur validation that is no longer relevant). The + // isFieldsValid check below will call onSubmitInvalid if the form is + // still invalid after re-validation. + if (this.baseStore.state.submissionAttempts <= 1) { + this.options.onSubmitInvalid?.({ + value: this.state.values, + formApi: this, + meta: submitMetaArg, + }) + return + } } this.baseStore.setState((d) => ({ ...d, isSubmitting: true })) diff --git a/packages/form-core/tests/FormApi.spec.ts b/packages/form-core/tests/FormApi.spec.ts index eccd2e544..5927aea19 100644 --- a/packages/form-core/tests/FormApi.spec.ts +++ b/packages/form-core/tests/FormApi.spec.ts @@ -1873,6 +1873,53 @@ describe('form api', () => { expect(formSubmit).toHaveBeenCalledOnce() }) + it('should run field-level onBlur validators on re-submission to clear stale errors', async () => { + // Regression: stale onBlur errors could prevent re-submission because + // canSubmit became false and _handleSubmit returned early before running + // validateAllFields, which would have re-evaluated and cleared the error. + // See: https://github.com/TanStack/form/issues/2034 + const onSubmit = vi.fn() + const onSubmitInvalid = vi.fn() + + const form = new FormApi({ + defaultValues: { type: 'PIN', pin: '' }, + onSubmit, + onSubmitInvalid, + }) + form.mount() + + // PIN field with an onBlur validator that is only required when type === 'PIN' + const pinField = new FieldApi({ + form, + name: 'pin', + validators: { + onBlur: ({ value }) => + form.getFieldValue('type') === 'PIN' && !value + ? 'PIN is required' + : undefined, + }, + }) + pinField.mount() + + // Simulate user touching and blurring the PIN field while type is 'PIN' + pinField.handleBlur() + expect(pinField.state.meta.errorMap.onBlur).toBe('PIN is required') + + // First submit: form is invalid, onSubmitInvalid is called + await form.handleSubmit() + expect(onSubmitInvalid).toHaveBeenCalledTimes(1) + expect(onSubmit).not.toHaveBeenCalled() + + // User switches type to 'Card' — PIN is no longer required + form.setFieldValue('type', 'Card') + + // Second submit: the onBlur error is stale (PIN field still has it), but + // re-running validators on submit should clear it since type !== 'PIN' + await form.handleSubmit() + expect(onSubmit).toHaveBeenCalledTimes(1) + expect(onSubmitInvalid).toHaveBeenCalledTimes(1) // not called again + }) + it('should run all types of async validation on fields during submit', async () => { vi.useFakeTimers() From 5145dec7c1eb814a3b920a4112d64ca791a907b5 Mon Sep 17 00:00:00 2001 From: restareaByWeezy Date: Sat, 11 Apr 2026 19:58:41 +0900 Subject: [PATCH 2/2] chore: add changeset for #2034 fix --- .changeset/fix-stale-onblur-resubmit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-stale-onblur-resubmit.md diff --git a/.changeset/fix-stale-onblur-resubmit.md b/.changeset/fix-stale-onblur-resubmit.md new file mode 100644 index 000000000..134c195b6 --- /dev/null +++ b/.changeset/fix-stale-onblur-resubmit.md @@ -0,0 +1,5 @@ +--- +'@tanstack/form-core': patch +--- + +fix(form-core): clear stale onBlur errors on re-submission