Race condition in validators if form value changes while `onChangeAsync` is running
Describe the bug
It appears that the onChangeAsync validator is not run if onChange reports an error for a field.
But if an onChangeAsync validator is running when the value changes, it will replace the errors from onChange which were sourced from the now previous value.
Your minimal, reproducible example
https://stackblitz.com/edit/tanstack-form-xgphkxqu?file=src%2Findex.tsx
Steps to reproduce
- Type
errorin the 'First name' field. Most of the time this will be shown as a valid value (bad) - After at least a second, add another character to the end of the field, It will now complain about the field containing an error (ok)
- After at least a second, delete that character and it will complain
cant be exact(ok) - After at least a second, quickly remove the last
rand re-add it. The field will briefly showcant be exactthen the error will disappear. (bad)
Some logging of performing 4 is provided in the screenshot below. This shows that the error from onChange('error') is used, until the onChangeAsync('erro') resolves, which then replaces the error
Expected behavior
As a user, I expect the validation errors from the latest value to be used, not the errors from the previous value.
How often does this bug happen?
Every time
Screenshots or Videos
Platform
- OS: Linux
- Browser: Chrome
- Version: 135.0.7049.95
TanStack Form adapter
None
TanStack Form version
v1.7.0
TypeScript version
No response
Additional context
This doesn't appear to happen if using validators on the field instead of form
The error maps are not separated by sync and async, so I'm not sure that this is an issue with the library. Could you elaborate on what prevents you from merging both validators into one async validator?
The problem isn't that the async error replaces the sync error (I could have mitigated that by performing the onChange checks inside onChangeAsync), its that its the async error from a previous value which replaces the sync error from the current value.
This means that it simply isn't safe to use both onChange and onChangeAsync at the same time.
This gave me another idea of something else to test, which I think shows the same issue.
https://stackblitz.com/edit/tanstack-form-muxgsl4w?file=src%2Findex.tsx
This case shows that there is no cancellation of a previous onChangeAsync when a new one runs.
In this case, if you type err, you will see the message slowly ticks through Complete from e, Complete from er and Complete from err, as expected.
Then backspace twice to change the field back to e, and it shows Complete from e followed by Complete from er, which is wrong.
So the error it shows is whichever promise resolved last, not whichever was started last.
Between these two test cases, I would say that the problem is more generally that a new run of the validators isn't cancelling previous runs.