Skip to content

Conversation

@jiji-hoon96
Copy link
Contributor

@jiji-hoon96 jiji-hoon96 commented Aug 14, 2025

Fixes validation errors not appearing on fields that mount after form initialization

Problem

When fields are mounted after the form's initial validation runs, validation errors from form-level validators (like [onMount]) are not displayed on these delayed fields, leading to inconsistent validation UX.

Solution

  • FormApi: Store all field errors (including for unmounted fields) in _allFieldErrors state
  • FieldApi: Sync existing errors from form state when fields mount, marking them as originating from form validation

Changes

  • Added _allFieldErrors property to BaseFormState to persist validation errors for all fields
  • Modified validateSync to store field errors for both mounted and unmounted fields
  • Enhanced FieldApi.mount() to sync existing errors from form state on field mount
  • Added comprehensive tests covering delayed mount, multiple fields, and remount scenarios

Closes #1630

@brandonleichty
Copy link

👀

@jiji-hoon96
Copy link
Contributor Author

@LeCarbonator I thought this issue needed to be resolved promptly, and I'm curious to hear your thoughts on the matter.

@brandonleichty
Copy link

I'm also quite curious about this one. I've had to use hidden in all of my forms, rather than conditional rendering. Which doesn't feel great—especially for future readers of the code.

@nx-cloud
Copy link

nx-cloud bot commented Sep 2, 2025

View your CI Pipeline Execution ↗ for commit 2289eae

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 1m 41s View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 25s View ↗

☁️ Nx Cloud last updated this comment at 2025-10-24 01:03:15 UTC

@LeCarbonator
Copy link
Contributor

I'm just not sure if this is the way to go about it. I've been tinkering behind the scenes to see what truly causes this, but I suppose I should focus more on it so it can get fixed

@pkg-pr-new
Copy link

pkg-pr-new bot commented Sep 2, 2025

More templates

@tanstack/angular-form

npm i https://pkg.pr.new/@tanstack/angular-form@1691

@tanstack/form-core

npm i https://pkg.pr.new/@tanstack/form-core@1691

@tanstack/form-devtools

npm i https://pkg.pr.new/@tanstack/form-devtools@1691

@tanstack/lit-form

npm i https://pkg.pr.new/@tanstack/lit-form@1691

@tanstack/react-form

npm i https://pkg.pr.new/@tanstack/react-form@1691

@tanstack/react-form-devtools

npm i https://pkg.pr.new/@tanstack/react-form-devtools@1691

@tanstack/solid-form

npm i https://pkg.pr.new/@tanstack/solid-form@1691

@tanstack/svelte-form

npm i https://pkg.pr.new/@tanstack/svelte-form@1691

@tanstack/vue-form

npm i https://pkg.pr.new/@tanstack/vue-form@1691

commit: 2289eae

@codecov
Copy link

codecov bot commented Sep 2, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.55%. Comparing base (6892ed0) to head (2289eae).
⚠️ Report is 39 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1691      +/-   ##
==========================================
+ Coverage   90.35%   90.55%   +0.19%     
==========================================
  Files          38       38              
  Lines        1752     1810      +58     
  Branches      444      470      +26     
==========================================
+ Hits         1583     1639      +56     
- Misses        149      151       +2     
  Partials       20       20              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jiji-hoon96
Copy link
Contributor Author

jiji-hoon96 commented Sep 3, 2025

I'm not entirely sure why the delayed-mounted field isn't receiving form validation either.

While my proposed method could resolve it, errors exist in both fieldMeta and _allFieldErrors. This raises concerns about duplicate states and memory overhead issues, as it would persistently store errors for all unmounted fields.

If you have any ideas, please share them so we can explore them together.

I have three potential approaches in mind

  1. Event-driven approach: Re-trigger form validation when the field mounts, re-executing the validation results only for this field.
  2. Store validation results as a function and evaluate them when needed.
  3. Subscribe to the form's validation state when the field mounts.

@LeCarbonator
Copy link
Contributor

The issue is that while mapping form validators to respective fields, it only iterates through the existing record of (previously registered) fields.
Ideally, it should have the following behaviour:

  • If a field error does not have an entry in the fieldInfo record, create one
  • If deleteField or removeValue is called, it should remove that entry

@jiji-hoon96
Copy link
Contributor Author

@LeCarbonator FormApi has been improved with a comprehensive error-handling approach that includes all field errors, and features have been added to automatically generate fieldInfo and prevent memory leaks in the deleteField method.

@brandonleichty
Copy link

@jiji-hoon96 does this mean the issue should be resolved?

@jiji-hoon96
Copy link
Contributor Author

@brandonleichty Yes!

@changeset-bot
Copy link

changeset-bot bot commented Sep 20, 2025

⚠️ No Changeset found

Latest commit: 2289eae

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

@LeCarbonator LeCarbonator left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! This is pretty close, but there are some things to address. If you'd like, I can help out with addressing them.

Comment on lines 1540 to 1568
if (fieldErrors) {
const allFieldErrors: Partial<
Record<DeepKeys<TFormData>, ValidationErrorMap>
> = this.state._allFieldErrors || {}
for (const [fieldName, fieldError] of Object.entries(fieldErrors)) {
if (fieldError) {
const typedFieldName = fieldName as DeepKeys<TFormData>
allFieldErrors[typedFieldName] = {
...allFieldErrors[typedFieldName],
[errorMapKey]: fieldError,
}
}
}
this.baseStore.setState((prev) => ({
...prev,
_allFieldErrors: allFieldErrors,
}))
}

const allFieldsToProcess = new Set([
...Object.keys(this.state.fieldMeta),
...Object.keys(fieldErrors || {}),
] as DeepKeys<TFormData>[])

for (const field of allFieldsToProcess) {
if (fieldErrors?.[field] && !this.fieldInfo[field]) {
this.getFieldInfo(field)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is going in the right direction, but _allFieldErrors is not the way. Iterating over previously mounted fields in fieldMeta is not enough for validation errors (like you correctly asserted), so ones that are missing should be added to it.

Fields derive their data from fieldMeta, so if they were added here, they would have the data not just on mount, but on construction.

One issue that we might run into with this approach is that wrong field names can result in a locked state. There is no field to read the stored error, so it is never visually shown. I don't think that's an issue because:

  • Our standard schemas are type safe when it comes to keys.
  • Standard schemas send a copy of the error list to the form error state, so it can be figured out if there's an issue.

Overall, I think the right way to go is to add missing fieldMeta from field errors if they're not present. Use the default field meta state for the other props being added.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the detailed feedback!
You're absolutely right. I've refactored the implementation to use fieldMeta instead of _allFieldErrors.

})
existingField.mount()

await new Promise((resolve) => setTimeout(resolve, 100))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a sleep helper from utils.ts you can use, along with vi.useFakeTimers(). See other async unit tests for details.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! I've replaced all setTimeout promises with the sleep helper from utils.ts and added vi.useFakeTimers() / vi.useRealTimers() to the async tests, following the pattern used in other test files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hidden field doesn't show errors on mount

3 participants