Skip to content

fix: revalidate nested fields after parent object updates#2125

Open
marc-niclas wants to merge 1 commit intoTanStack:mainfrom
marc-niclas:codex/fix-tanstack-form-issue-2113
Open

fix: revalidate nested fields after parent object updates#2125
marc-niclas wants to merge 1 commit intoTanStack:mainfrom
marc-niclas:codex/fix-tanstack-form-issue-2113

Conversation

@marc-niclas
Copy link
Copy Markdown

@marc-niclas marc-niclas commented Apr 15, 2026

🎯 Changes

Fixes the stale nested field meta described in #2113.

When setFieldValue updates a parent object field like a, mounted descendant fields like a.b are now revalidated on change as well. This keeps nested field-level errors in sync after programmatic parent object updates.

Added a regression test covering the reported case where updating a from { b: 0 } to { b: 1 } should clear the existing validation error on a.b.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

Bug Fixes

  • Fixed an issue where nested field validation errors would become stale when updating a parent object field. Nested descendant fields are now properly revalidated whenever their parent object changes, ensuring that validation errors, validity states, and field values remain accurate and in sync with your data.

- Fixes the stale nested field meta described in TanStack#2113.
- When `setFieldValue` updates a parent object field like `a`, mounted descendant fields like `a.b` are now revalidated on change as well. This keeps nested field-level errors in sync after programmatic parent object updates.
- Added a regression test covering the reported case where updating `a` from `{ b: 0 }` to `{ b: 1 }` should clear the existing validation error on `a.b`.

Fixes TanStack#2113
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

This PR adds a patch release to @tanstack/form-core that prevents stale nested field errors when updating a parent object field. The implementation extends setFieldValue to scan for and revalidate descendant fields when the parent field updates, with accompanying test coverage.

Changes

Cohort / File(s) Summary
Changesets Entry
.changeset/stale-nested-field-meta.md
Patch release notes documenting an error-handling fix for stale nested field errors when updating parent object fields.
Form Validation Logic
packages/form-core/src/FormApi.ts
Extended setFieldValue to scan fieldInfo for descendant fields and conditionally revalidate them via their FieldApi instances when the parent field value changes.
Test Coverage
packages/form-core/tests/FormApi.spec.ts
Added test verifying that nested field errors and validity state are cleared when a parent object field is updated via setFieldValue.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Nested fields once led astray,
Now revalidate the proper way!
When parents change their nested soul,
Descendants update—errors whole. 🌱✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main fix: revalidating nested fields after parent object updates, which directly matches the core change in the pull request.
Description check ✅ Passed The description includes all required sections from the template with complete information: clear explanation of changes and motivation, all checklist items addressed, and release impact properly documented.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
packages/form-core/src/FormApi.ts (1)

2287-2297: Defer descendant lookup until validation is enabled.

descendantFields is only used inside if (!dontValidate), but this scan now runs on every setFieldValue call. That makes array helpers in this file pay an unnecessary O(field count) walk even when they explicitly skip validation first.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/form-core/src/FormApi.ts` around lines 2287 - 2297, The current
eager computation of descendantFields (using fieldString and this.fieldInfo)
runs on every setFieldValue call even when validation is skipped; move the
descendantFields calculation and any dependent logic into the block guarded by
if (!dontValidate) so the Object.keys(this.fieldInfo).filter(...) scan only runs
when validation is enabled, keeping fieldString creation and descendant lookup
deferred until needed (ensure any references to descendantFields are updated
accordingly inside that block).
packages/form-core/tests/FormApi.spec.ts (1)

228-260: Add an array-path regression for the bracket branch too.

The implementation now has separate descendant matching for object paths (a.b) and array paths (a[0].b), but this test only covers the dot-path case. A small users[0].name variant would keep the new fieldString + '[' branch covered as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/form-core/tests/FormApi.spec.ts` around lines 228 - 260, Add a
parallel test that covers the array-path branch: duplicate the "should clear
nested field errors when setting a parent object field" case but use an
array-style path (e.g. defaultValues: { users: [{ name: '' }] } and FieldApi
name 'users[0].name' or defaultValues: { a: [{ b: 0 }] } and FieldApi name
'a[0].b'), mount FormApi and FieldApi, trigger the validator to produce an error
(setValue to a failing value), then call form.setFieldValue on the parent array
object (e.g. setFieldValue('users', [{ name: 'ok' }] ) or setFieldValue('a', [{
b: 1 }])) and assert the child field value updates and that
childField.state.meta.errors is cleared and childField.state.meta.isValid is
true to cover the bracket-path branch in FieldApi/FormApi.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/form-core/src/FormApi.ts`:
- Around line 2328-2330: The loop calling
this.getFieldInfo(descendantField).instance?.validate('change') causes N
repeated form-level validations because FieldApi.validate triggers form
sync/async validation; replace this direct per-descendant validate call with a
single helper on FormApi (e.g., a refreshDescendants or queueDescendantChanges)
that collects descendant field keys and calls the form-level validation once or
uses the existing form-level validation runner to revalidate all affected fields
in one pass; modify the code path around descendantFields iteration in FormApi
to call that helper instead of FieldApi.validate, ensuring you reuse the
form-level validation runner (the same routine used by FieldApi.validate) to
avoid abort/restart churn.

---

Nitpick comments:
In `@packages/form-core/src/FormApi.ts`:
- Around line 2287-2297: The current eager computation of descendantFields
(using fieldString and this.fieldInfo) runs on every setFieldValue call even
when validation is skipped; move the descendantFields calculation and any
dependent logic into the block guarded by if (!dontValidate) so the
Object.keys(this.fieldInfo).filter(...) scan only runs when validation is
enabled, keeping fieldString creation and descendant lookup deferred until
needed (ensure any references to descendantFields are updated accordingly inside
that block).

In `@packages/form-core/tests/FormApi.spec.ts`:
- Around line 228-260: Add a parallel test that covers the array-path branch:
duplicate the "should clear nested field errors when setting a parent object
field" case but use an array-style path (e.g. defaultValues: { users: [{ name:
'' }] } and FieldApi name 'users[0].name' or defaultValues: { a: [{ b: 0 }] }
and FieldApi name 'a[0].b'), mount FormApi and FieldApi, trigger the validator
to produce an error (setValue to a failing value), then call form.setFieldValue
on the parent array object (e.g. setFieldValue('users', [{ name: 'ok' }] ) or
setFieldValue('a', [{ b: 1 }])) and assert the child field value updates and
that childField.state.meta.errors is cleared and childField.state.meta.isValid
is true to cover the bracket-path branch in FieldApi/FormApi.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eb54d83b-a972-4b48-8d63-ec34ddefb943

📥 Commits

Reviewing files that changed from the base of the PR and between 254f157 and da0d427.

📒 Files selected for processing (3)
  • .changeset/stale-nested-field-meta.md
  • packages/form-core/src/FormApi.ts
  • packages/form-core/tests/FormApi.spec.ts

Comment on lines +2328 to +2330
descendantFields.forEach((descendantField) => {
this.getFieldInfo(descendantField).instance?.validate('change')
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This revalidates the whole form once per descendant.

FieldApi.validate() also runs form sync/async validation (packages/form-core/src/FieldApi.ts:1904-1932), so a single parent update now fans out into repeated form revalidation and async abort/restart churn. On large nested forms or async onChange validators, that can turn one change into N+1 form validations. Please route descendant refresh through a helper that reuses the single form-level pass instead of calling instance.validate('change') directly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/form-core/src/FormApi.ts` around lines 2328 - 2330, The loop calling
this.getFieldInfo(descendantField).instance?.validate('change') causes N
repeated form-level validations because FieldApi.validate triggers form
sync/async validation; replace this direct per-descendant validate call with a
single helper on FormApi (e.g., a refreshDescendants or queueDescendantChanges)
that collects descendant field keys and calls the form-level validation once or
uses the existing form-level validation runner to revalidate all affected fields
in one pass; modify the code path around descendantFields iteration in FormApi
to call that helper instead of FieldApi.validate, ensuring you reuse the
form-level validation runner (the same routine used by FieldApi.validate) to
avoid abort/restart churn.

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.

1 participant