_handleSubmit gates form-level validation behind field-level validity, causing incomplete error reports on submit
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 6.7k
- Forks
- 682
- Avg merge
- 5d 18h
- Merged PRs (30d)
- 7
Description
Describe the bug
I believe this is a bug, though I want to flag upfront that the staging in _handleSubmit seems to be intentional. Regardless I'd like to argue the current behavior still isn't right.
_handleSubmit runs validation in two stages with an early return between them:
await this.validateAllFields('submit') // Stage 1: field-level validators
if (!this.state.isFieldsValid){ /* ... */ return } // Gate
await this.validate('submit') // Stage 2: form-level validators
If Stage 1 fails, users only see the errors from this stage. So any other invalid fields stay silent until these errors are fixed. This contradicts what users intuitively expect from a submit action: check everything and show all errors.
Setting canSubmitWhenInvalid: true also doesn't fix this. The flag gates whether _handleSubmit runs at all, not how validation is staged within it.
My use case:
I only use form-level validation and canSubmitWhenInvalid: true.
Form-level validators write their errors into individual fields' errorMap. But isFieldsValid reads those entries without distinguishing source. So after a failed submit, those form-level errors count as "field-level invalid" on the next attempt — the isFieldsValid gate triggers, form-level validation is skipped, and stale errors stay on screen while new ones never appear.
This mechanism appears to be the underlying cause / relevant for several issues that have been reported separately: #1874, #1663 (see TeChn4K's reproduction there), #2034
PR #2120 in response to #2034 addresses a narrower symptom and doesn't solve the core issue: In #2034 the user has an onBlur validation on a field with conditional validation logic based on another field. The reported issue can be fixed by linking the other field with onBlurListenTo or using canSubmitWhenInvalid: true. The PR does not touch the isFieldsValid gate, so it does not address the problems above.
Your minimal, reproducible example
https://codesandbox.io/p/sandbox/tanstack-form-bug-7yxscz
Steps to reproduce
- Enter a value for field a, leave field b empty
- submit → Field b shows an error since it's empty
- clear field a
- submit again (notice
canSubmitWhenInvalidis set to true) → only field b shows an error even though field a is also empty
Expected behavior
On submit, both stages should run unconditionally and isValid should be evaluated once over the combined result:
await this.validateAllFields('submit')
await this.validate('submit')
if (!this.state.isValid) {
// onSubmitInvalid with the full error picture
return
}
// onSubmit
Why I think this is the right behavior:
- It matches user intuition for submit. Submit is the moment users most need a complete error report. Partial reports force them to fix-submit-fix-submit until they've discovered every problem, which is worse UX than showing everything at once.
- When
canSubmitWhenInvalidisfalse(the default), existing field errors already prevent_handleSubmitfrom running, so this change should have no effect unless there are filed level validators in which case all errors would now surface on submit instead of only the field-level ones, which I'd argue is also the right behavior. It changes behavior for forms that explicitly opt intocanSubmitWhenInvalid: true— where a complete error report on every submit is exactly what you'd expect.
Happy to open a PR if there's a decision on this.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
not relevant
TanStack Form adapter
react-form
TanStack Form version
1.28.0
TypeScript version
No response
Additional context
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start at _handleSubmit in the React Form adapter and reproduce the behavior with the linked CodeSandbox. Trace validateAllFields('submit'), the isFieldsValid gate, and validate('submit'); done means submit validation reports errors from both stages and the invalid-submit path receives the combined result.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- react, typescript
- Domain
- frontend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100