form icon indicating copy to clipboard operation
form copied to clipboard

Race condition in validators if form value changes while `onChangeAsync` is running

Open Julusian opened this issue 8 months ago • 2 comments

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

  1. Type error in the 'First name' field. Most of the time this will be shown as a valid value (bad)
  2. 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)
  3. After at least a second, delete that character and it will complain cant be exact (ok)
  4. After at least a second, quickly remove the last r and re-add it. The field will briefly show cant be exact then 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

Image

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

Julusian avatar Apr 25 '25 15:04 Julusian

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?

LeCarbonator avatar Apr 25 '25 21:04 LeCarbonator

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.

Julusian avatar Apr 25 '25 22:04 Julusian