diff --git a/.changeset/quiet-submit-guard.md b/.changeset/quiet-submit-guard.md new file mode 100644 index 0000000000..cd05ad2fdb --- /dev/null +++ b/.changeset/quiet-submit-guard.md @@ -0,0 +1,5 @@ +--- +'@tanstack/form-core': patch +--- + +Prevent concurrent form submissions while an async submit is already in progress. diff --git a/packages/form-core/src/FormApi.ts b/packages/form-core/src/FormApi.ts index 4400017d30..f0bfe56e10 100644 --- a/packages/form-core/src/FormApi.ts +++ b/packages/form-core/src/FormApi.ts @@ -2418,6 +2418,8 @@ export class FormApi< * Handles the form submission, performs validation, and calls the appropriate onSubmit or onSubmitInvalid callbacks. */ _handleSubmit = async (submitMeta?: TSubmitMeta): Promise => { + if (this.state.isSubmitting) return + this.baseStore.setState((old) => ({ ...old, // Submission attempts mark the form as not submitted diff --git a/packages/form-core/tests/FormApi.spec.ts b/packages/form-core/tests/FormApi.spec.ts index c57bb044e8..f3aa9f0695 100644 --- a/packages/form-core/tests/FormApi.spec.ts +++ b/packages/form-core/tests/FormApi.spec.ts @@ -3521,6 +3521,38 @@ describe('form api', () => { expect(form.state.isSubmitSuccessful).toBe(false) }) + it('should not call onSubmit again while a submission is in progress', async () => { + vi.useFakeTimers() + try { + const submitDelayMs = 1000 + const expectedSubmitCount = 1 + const onSubmit = vi.fn(async () => { + await sleep(submitDelayMs) + }) + const form = new FormApi({ + defaultValues: { + name: 'test', + }, + onSubmit, + }) + + form.mount() + + const firstSubmit = form.handleSubmit() + await vi.advanceTimersByTimeAsync(0) + + const secondSubmit = form.handleSubmit() + await vi.advanceTimersByTimeAsync(0) + + expect(onSubmit).toHaveBeenCalledTimes(expectedSubmitCount) + + await vi.runAllTimersAsync() + await Promise.all([firstSubmit, secondSubmit]) + } finally { + vi.useRealTimers() + } + }) + it('should reset the fields value and meta to default state', async () => { const form = new FormApi({ defaultValues: {