form icon indicating copy to clipboard operation
form copied to clipboard

Hidden field doesn't show errors on mount

Open marc-at-brightnight opened this issue 6 months ago • 11 comments

Describe the bug

I have my form fields in a Popper, meaning my form object mounts before my fields. This leads to the situation where the field level errors are not displayed when the popper opens. There may be a simple solution to this but I've been playing around and haven't found yet. Any help would be appreciated.

Your minimal, reproducible example

https://stackblitz.com/edit/vitejs-vite-8bcuwtpv?file=src%2FApp.tsx

Steps to reproduce

  1. hit "show field"
  2. observe no red text below input field
  3. type and clear out text
  4. red error will appear

Expected behavior

I expect the red text to appear immediately after clicking show field

How often does this bug happen?

None

Screenshots or Videos

No response

Platform

  • macOS
  • brave 1.78.97

TanStack Form adapter

None

TanStack Form version

1.14.2

TypeScript version

No response

Additional context

No response

marc-at-brightnight avatar Jul 24 '25 20:07 marc-at-brightnight

Actually the behavior you're seeing is correct based on how you've configured the form. You've defined a form-level onMount, but you're expecting field-level onMount behavior.

I've forked your sandbox

kusiewicz avatar Jul 28 '25 16:07 kusiewicz

Mhm ok. I guess my only concern would be that this could get verbose quickly, for many fields. But I guess I can limit it to those fields that mount after the form and that I know will have an error on mount.

Thanks much!

marc-at-brightnight avatar Jul 28 '25 20:07 marc-at-brightnight

@kusiewicz one more question: I notice this fix does not work with custom errors defined with zod's .check.

I created a new reproduction: https://stackblitz.com/edit/vitejs-vite-9bzkjdnu?file=src%2FApp.tsx

After you click the 'show' button, the text 'Age must be defined' is missing from the field level error for the age field. This error does exist at the form level, due to the .check I defined.

Any feedback on this one?

marc-at-brightnight avatar Jul 31 '25 15:07 marc-at-brightnight

Actually I've just run into a similar problem.

So you're logging out onMount form errors - they actually exist immediately - before Show btn is clicked. But its not synced with field errors. When hidden fields are mounted they have empty error fields (field.state.meta.errors).

One way is to display formErrors from formErrorMap - because formState has those errors right away.

https://stackblitz.com/edit/vitejs-vite-n5ak5khm?file=src%2FApp.tsx

But i think its weird since late form validators (e.g onChange) updates field state. I am now wondering whether errors from onMount at the form level should not synchronize the initial errors of fields even if they are not mounted.

@LeCarbonator any thoughts? :)

kusiewicz avatar Aug 03 '25 09:08 kusiewicz

Okay did some digging in the code and its look like that:

  1. When App is rendered useForm is called. At this moment library creates form instance and immediately runs form onMount validation.
  2. In the same time when onMount validation is run, our show state has value false. It means that <form.Field> components are not mounted yet. Form tries assign validation errors to particular fields, but those fields does not exists yet at all.
  3. You're clicking show - state changes to true.
  4. React mounts <form.Field> components. Each field is being registered in the form. But onMount validation happened on form level in the step 1 and will not be called again. Fields are mounted with clear meta state since at the moment of theirs registration, form does not have "waiting" errors for them.

When show is true by default - fields mounts at the same moment like the form, so onMount validation founds its targets right away and assigns them errors.

Maybe in cases like that the best way is simply call form.ValidateAllFields() after switching state?

kusiewicz avatar Aug 03 '25 16:08 kusiewicz

I think the mounted state of fields shouldn't matter if there are errors assigning them. Otherwise, you risk using field-level errors at all.

I'll look into it in a bit!

LeCarbonator avatar Aug 03 '25 16:08 LeCarbonator

I think the mounted state of fields shouldn't matter if there are errors assigning them. Otherwise, you risk using field-level errors at all.

I'll look into it in a bit!

I think that regardless of whether the fields are mounted or not, they should always receive their initial errors from the first onMount

kusiewicz avatar Aug 03 '25 19:08 kusiewicz

I'm running into this exact same issue in my current project, and had to rewrite everything so that fields aren't mounted/unmounted. As a workaround, I'm using the hidden HTML attribute—which doesn't feel great.

Definitely curious to see what the fix will be for this.

brandonleichty avatar Aug 05 '25 20:08 brandonleichty

@marc-at-brightnight @LeCarbonator

This is obviously less-than-ideal, but hopefully it provides some insights into the issue.

Looking at Marc's Stackblitz, if you add:

function toggleVisibility() {
  flushSync(() => {
    setShow((v) => !v);
  });
  form.validate("change");
}

And then apply that to the button:

<button onClick={toggleVisibility}>
  {show ? "Hide" : "Show"} Field
</button>

The form behaves as expected...

brandonleichty avatar Aug 10 '25 06:08 brandonleichty

function toggleVisibility() {
  flushSync(() => {
    setShow((v) => !v);
  });
  form.validate("change");
}

And then apply that to the button:

<button onClick={toggleVisibility}>
  {show ? "Hide" : "Show"} Field
</button>

The form behaves as expected...

Yeah this is what I ended up doing, at least the validate part. I think it's fine as a workaround, validation is pretty fast for my use cases. I'm fine to close this as completed.

marc-at-brightnight avatar Oct 07 '25 22:10 marc-at-brightnight

I am running into this issue, or something very similar. I may just be confused about how unmounted fields work.

my setup: Image

I have a field group that's got 2 fields in a popover, so they are only mounted when the popover is open. There's a checkbox (outside the popover) that enables/disables the field.

I have validation on the fields and I'm using onChangeListenTo to include the checkbox in the validation (and bypass it). This all works fine when the popover is open, but when it's closed and I click on the checkbox, only the first validation change is saved to the field's state. If I continue to toggle the checkbox, the validation function runs every (visible in console), but any errors it produces are lost and the field's meta state is still valid.

I modified the example to show this issue: https://stackblitz.com/edit/vitejs-vite-gqyskrvt?file=src%2FApp.tsx

The workaround above doesn't work, the validation function is running everytime as expected, but the result is discarded if the form is unmounted. I would like to show the validation status when the checkbox is toggled, even if the popover is closed.

EDIT:

it seems that if I move the validation to the form level instead of field level, then the errors are saved and displayed properly, so this is one workaround.

bj0 avatar Oct 17 '25 20:10 bj0