fix: sync validation errors for delayed-mounted fields
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
_allFieldErrorsstate - FieldApi: Sync existing errors from form state when fields mount, marking them as originating from form validation
Changes
- Added
_allFieldErrorsproperty 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
👀
@LeCarbonator I thought this issue needed to be resolved promptly, and I'm curious to hear your thoughts on the matter.
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.
View your CI Pipeline Execution ↗ for commit 23bfa578ed45ee64dc2544aa74fa988ddce95c4c
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:sherif,test:knip,tes... |
✅ Succeeded | 57s | View ↗ |
nx run-many --target=build --exclude=examples/** |
✅ Succeeded | <1s | View ↗ |
☁️ Nx Cloud last updated this comment at 2025-12-18 10:09:17 UTC
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
More templates
- @tanstack/form-example-angular-array
- @tanstack/form-example-angular-large-form
- @tanstack/form-example-angular-simple
- @tanstack/form-example-angular-standard-schema
- @tanstack/form-example-lit-array
- @tanstack/form-example-lit-simple
- @tanstack/form-example-lit-standard-schema
- @tanstack/form-example-lit-ui-libraries
- @tanstack/form-example-react-array
- @tanstack/form-example-react-compiler
- @tanstack/form-example-react-devtools
- @tanstack/form-example-react-dynamic
- @tanstack/field-errors-from-form-validators
- @tanstack/form-example-react-large-form
- @tanstack/form-example-react-next-server-actions
- @tanstack/form-example-react-query-integration
- @tanstack/form-example-remix
- @tanstack/form-example-react-simple
- @tanstack/form-example-react-standard-schema
- @tanstack/form-example-react-tanstack-start
- @tanstack/form-example-react-ui-libraries
- @tanstack/form-example-svelte-array
- @tanstack/form-example-svelte-large-form
- @tanstack/form-example-svelte-simple
- @tanstack/form-example-svelte-standard-schema
- @tanstack/form-example-vue-array
- @tanstack/form-example-vue-simple
- @tanstack/form-example-vue-standard-schema
- @tanstack/form-example-solid-array
- @tanstack/form-example-solid-devtools
- @tanstack/form-example-solid-large-form
- @tanstack/form-example-solid-simple
- @tanstack/form-example-solid-standard-schema
@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/react-form-nextjs
npm i https://pkg.pr.new/@tanstack/react-form-nextjs@1691
@tanstack/react-form-remix
npm i https://pkg.pr.new/@tanstack/react-form-remix@1691
@tanstack/react-form-start
npm i https://pkg.pr.new/@tanstack/react-form-start@1691
@tanstack/solid-form
npm i https://pkg.pr.new/@tanstack/solid-form@1691
@tanstack/solid-form-devtools
npm i https://pkg.pr.new/@tanstack/solid-form-devtools@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: 23bfa57
Codecov Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 89.43%. Comparing base (6892ed0) to head (23bfa57).
:warning: Report is 102 commits behind head on main.
Additional details and impacted files
@@ Coverage Diff @@
## main #1691 +/- ##
==========================================
- Coverage 90.35% 89.43% -0.93%
==========================================
Files 38 48 +10
Lines 1752 1940 +188
Branches 444 487 +43
==========================================
+ Hits 1583 1735 +152
- Misses 149 184 +35
- Partials 20 21 +1
:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.
:rocket: New features to boost your workflow:
- :snowflake: Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
- :package: JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
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
- Event-driven approach: Re-trigger form validation when the field mounts, re-executing the validation results only for this field.
- Store validation results as a function and evaluate them when needed.
- Subscribe to the form's validation state when the field mounts.
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
deleteFieldorremoveValueis called, it should remove that entry
@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.
@jiji-hoon96 does this mean the issue should be resolved?
@brandonleichty Yes!
🦋 Changeset detected
Latest commit: 23bfa578ed45ee64dc2544aa74fa988ddce95c4c
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 13 packages
| Name | Type |
|---|---|
| @tanstack/form-core | Patch |
| @tanstack/angular-form | Patch |
| @tanstack/form-devtools | Patch |
| @tanstack/lit-form | Patch |
| @tanstack/react-form | Patch |
| @tanstack/solid-form | Patch |
| @tanstack/svelte-form | Patch |
| @tanstack/vue-form | Patch |
| @tanstack/react-form-devtools | Patch |
| @tanstack/solid-form-devtools | Patch |
| @tanstack/react-form-nextjs | Patch |
| @tanstack/react-form-remix | Patch |
| @tanstack/react-form-start | Patch |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR