formIsValid stays false despite all fields being valid?
I have a large form with field-level validation configured to run on blur. I've run into a tricky issue where
formIsValid
remains false even when all individual fields show isValid: true
.
The problem flow:
1. User fills out form and selects a radio button (the final field)
2. User clicks Save → nothing happens because formIsValid
is false (even though there are no field errors)
3. User clicks back into any text field and tabs out (triggering blur)
4. User clicks Save → now it works because formIsValid finally becomes true
What I've tried:
I expected await form.handleSubmit()
to re-run all validation and update formIsValid, but it seems to run the validation without updating the formIsValid
property?
Root cause I discovered:
The issue is browser-specific behavior with radio buttons. When a user clicks Save after selecting a radio button:
Chrome: Focuses the radio button, so blur events work as expected
Safari: Doesn't focus the radio button, so no blur event fires, meaning validation never runs for that field
This means in Safari, the radio button validation never triggers, keeping formIsValid false even though the field value is valid.
Question:
Is there a way to manually trigger the validation needed to update formIsValid? Or force all field validations to run and update the form's validity state before submit?
Happy to share code snippets if that would help troubleshoot!
Note: I'm using Zod for form validation, and TanStack form composition with useAppForm
, withForm
, etc.
35 Replies
extended-salmonOP•5w ago
In Safari, when clicking a radio button, and then clicking "Save" - no blur happens (but it does happen in Chrome). This blur seems to need to happen for
isFormValid
to be true
.correct-apricot•5w ago
could you share the radio button snippet? I didn't know about this parity between safari and chrome
in general,
handleSubmit
will return early if canSubmit
is false. If canSubmitWhenInvalid is true, it will rerun all validation regardless of canSubmit
. Note that it won't trigger onSubmit
unless those validators passed.
Determining the cause for the onBlur behaviour is still important though, so I'd appreciate a snippet to test against.extended-salmonOP•5w ago
Yeah I can try to get you a snippet tomorrow. Though, this is a custom component/radio button. I haven't tried it with a vanilla HTML radio button that doesn't have custom styling.
That being said, let's say I have 10 form inputs, and
isValid
is true
on all of them when the user clicks "Save", and handleSubmit
is run, why would formIsValid
and canSubmit
be false
if the validation is re-run?
It's weird that I can make both formIsValid
and canSubmit
by just going to another field, and then bluring--and then the Save button and handleSubmit
workcorrect-apricot•5w ago
I see. Which UI library do you use?
There are some libraries that like to overcorrect some stuff. One that comes to mind is Bootstrap and their number inputs, so I can look into the library too.
It wouldn't explain why it works on Chrome though ...
It implies that the
field.handleBlur
wasn't called for safariextended-salmonOP•5w ago
It's an internal one at my company. But I think the idea is the same as something like Bootstrap though.
That being said, if
isValid
is true on the field - and it's being validated, is there any reason that canSubmit
and formIsValid
would still be false
?
Here's something I threw together to troubleshoot, and you can see that each and every field is valid.
But canSubmit
and formIsValid
are both false
(that is, until I go a text/input field, and blur).
Is there a way to programmatically say "run the blur validation on every field"?correct-apricot•5w ago
Is there a way to programmatically say "run the blur validation on every field"?there is! form.validateAllFields
That being said, if isValid is true on the field - and it's being validated, is there any reason that canSubmit and formIsValid would still be false?Not that I can think of - This will need some testing. Thanks for the report!
extended-salmonOP•5w ago
I did try
form.validateAllFields
, and it didn't make any difference.
Even tried manually validating the fields before calling handleSubmit
:
It only seems to flip canSubmit
and formIsValid
when I actually blur a fieldcorrect-apricot•5w ago
perhaps there's a lingering field? do you have any dynamic sections in the form?
actually, that is likely what's going on.
formIsValid
means the form itself has errors, but you mentioned that no field actually has problems and they're all valid
Zod schemas will create an error object and send a copy to the form, so you should be able to read all issues that were mapped to fields in formIsValid
and form.state.errors
However, if there's no field that actually matches the field name (zod's generated path might mismatch), then it's only present on the form and not on any field
Compare that to isFieldsValid
which represents all field errors currently presentextended-salmonOP•5w ago
Does this explain why everything works as expected if I fill in all of the fields, and then just make sure I blur out of any field before clicking "Save" - and then it works?
correct-apricot•5w ago
-# not even a little.
It should help with making a reproducible example though! I'll give it a shot later
extended-salmonOP•5w ago
Cool! I'll try and send some examples/videos tomorrow (it's getting a bit late my time). Really appreciate your help!!
correct-apricot•5w ago
no rush :Prayge: hopefully the suggested workaround will do until it's resolved
extended-salmonOP•5w ago
I might have missed it. What was the suggested work around?
And after doing some Googling, it definitely looks like Safari handles focusing on radio buttons differently. I’ll try to create a reproducible example tomorrow
Oh you’re saying that the Zod schema might have an error—but might not map to any field?
I can definitely quadruple check that. I just find it weird that it works perfectly fine in Chrome when the blurring happens on each field.
correct-apricot•5w ago
canSubmitWhenInvalid
will allow you to submit with canSubmit: false
and will rerun the validators, hopefully checking that your form is indeed valid at submission time. That's the suggested workaround
yeah, that's the part that isn't explained by the zod error. It might explain why fields can be valid before zod realized it's fine now (because the blur validation hasn't run yet)
because the errors that were generated weren't pointed at an existing fieldextended-salmonOP•5w ago
Do you know of an easy way to output/compare the zod schema to my fields so that I can troubleshoot that?
correct-apricot•5w ago
add a form listener that outputs
yourSchema.safeParse(value)
to console
lists all the issues
alternatively, use a store to get the current form-level errors:
which should be a formatted version of the zod errors
looking back ... you're setting field meta directly here. Maybe that's causing the desync between form and field state.
That should be easy to test though, I'll make an example real quick to testcorrect-apricot•5w ago
it was indeed because of
setMeta
. Since I haven't seen this type of custom validation logic before, I've gone ahead and made an example listing three versions: yours, mine, and a second / third suggestion. Feel free to ask for more info: https://stackblitz.com/edit/vitejs-vite-owpjfazk?file=src%2FApp.tsx
@brandonleichtyLeCarbonator
StackBlitz
Vitejs - Vite (duplicated) - StackBlitz
Next generation frontend tooling. It's fast!
extended-salmonOP•4w ago
@Luca | LeCarbonator WOW...
First of all, I can't believe you took the time to put all of those examples together for me. Truly amazing! Let me know if I can buy a "virtual" coffee or something!
I'm so glad I shared that full code snippet, as I was totally looking in the wrong spot. I can't believe that's what it was.
Out of your three options, is there one that you'd recommend or you feel like is the most "TanStack Form" way to handle this?
Again, thnank you thank you!!
correct-apricot•4w ago
Well, I don't have a coffee thingy set up, so don't worry about that!
As for what my suggestion is, it depends on what your validators really are. If it's really just a zod schema (especially zod v4), go for version 3. The schema is quick to run, you won't run into issues where
canSubmit
is false for too long, but the visual errors shouldn't show up for the user.extended-salmonOP•4w ago
It is a Zod 4 schema... though it's pretty complex. This form has a lot of conditional logic, and I haven't found a way to get it to work outside of this pattern I'm using...
correct-apricot•4w ago
true, but unless you benchmarked it, I doubt it's actually the halting bit of your code. If that's the concern, then using React in the first place is a problem
TanStack Form isn't particularly optimized either, it focuses on less rerenders at the expense of RAM at the moment
extended-salmonOP•4w ago
Yeah I'm not super worried about performance. As I'm sure it's more than fast enough!
On a semi-unrelated note (by on the topic of Zod), would what you say I'm doing for conditional fields is a solid approach? I need runtime checks, and I couldn't get a Zod discriminated union to work in my usecase
correct-apricot•4w ago
I couldn't get Zod discriminated union to work in my usecaseAs in the types broke? or runtime?
extended-salmonOP•4w ago
Seemed to be a runtime problem. Couldn't get any of the errors to appear
correct-apricot•4w ago
I mean, this is a free chance to give zod v4 a spin. Do you have an example object to compare against?
extended-salmonOP•4w ago
I think I am using Zod 4? The import I'm using (in the file above) is
import * as z from "zod/v4";
correct-apricot•4w ago
it‘s the beta version of zod v4 yeah
full release has been out for I think a month?
oh, didn‘t answer your question
the schema is solid, but make sure to unit test
extended-salmonOP•4w ago
Oh I think I just realized why I was removing the actual errors before...
I want the errors to also appear if the user clicks "Save"/submit - even if no fields were blurred.
Now the challenge I'm running into is that the errors don't go away when a user clicks "Save" - and goes back to address them. As the submission attempts is great than 0. This is the logic I'm using in my custom form components:
correct-apricot•4w ago
hmm, I see … this‘ll be tricky
extended-salmonOP•4w ago
What about something like this?
This appears to work - but I feel like I might be missing something obvious...
correct-apricot•4w ago
does
isDirty
reset if submission attempts is bigger than 0? I don't think it does
either way, I would rely on a useRef
instead to keep track of it. You don't need it to be reactive since it's purely dependent on other reactive states
I'll write something and see if it works 👍
How about something like this? @brandonleichty
hmmm ... not quite ... if a user submits without having interacted with any field, then isBlurred
will start out as false and therefore won't reset forceShowError
on change
the main issue here is that you want to clear the error as soon as any change happens, which I haven‘t seen before here. I‘ll think about it for a bit, maybe something comes to mind
if not, then it‘s something to remember for the validation feature coming soon
I guess instead of doing all that blur setting shenanigans, you could also just subscribe to the field value with useEffect
set the meta of isBlurred in that hook too so you don‘t forget in the form listener
I've tinkered with the solution last night, and it was quite difficult to get it right. The immediate removal of the field error would require keeping track of change, blur as well as submission which would need more logic worked into it.
However, I have a proposal that you can try out which is based on Reward Early, Punish Late.
Essentially this flow instead of your proposed one:
* If a value is invalid, as soon as the user fixes the input it should be green again (reward early).
* If a value is valid, it shouldn't become red until the user finishes its input -- on blur or submission.
That reduced it to only three main states: submissionAttempts, blurred state and isValid. It also flows quite nicely from the UX perspective. As far as screen readers go, an early removal of the error might actually convey the wrong information, as it's not actually a valid field yet. You may end up in a loop of changing 1 character and blurring it, only to find out that the new value wasn't actually correct at all.correct-apricot•4w ago
here's a screencast of what the behaviour would be with the proposed strategy. Let me know if you still want the eager removal of errors @brandonleichty
correct-apricot•4w ago
this implementation should also be safe if you have dynamic fields, such as a checkbox for e. g.
has Start date
, but not actually providing a start date. It would look to the user like it's valid, until they try to submit and see that it has errorsextended-salmonOP•4w ago
Hey @Luca | LeCarbonator- I somehow missed the notification for this message! Sorry I'm just responding.
Wow, you really went above and beyond here! Can't thank you enough.
I am curious... Is it not uncommon for people to want to show errors on blur, and then remove them when the user starts typing? That's been a common pattern that UX/AX teams that I've worked with have recommended.
In terms of my original issue, I ended up going this route, which fixed my weird Safari radio button focusing issue. And all works as expected now. Though, I probably need to do some research into why the
canSubmit
wasn't flipping to true
in my case...
I was wondering: is it possible to have multiple global validators? I couldn't seem to get that to work. For example:
correct-apricot•4w ago
yeah, you can have multiple. Just keep in mind that they don‘t rerun the same either
so an onBlur error would not be cleared from onChange