Skip to content

fix(FieldApi): fix race condition when using onChangeListenTo#2143

Open
Pascalmh wants to merge 4 commits intoTanStack:mainfrom
Pascalmh:bugfix/onChangeListenTo
Open

fix(FieldApi): fix race condition when using onChangeListenTo#2143
Pascalmh wants to merge 4 commits intoTanStack:mainfrom
Pascalmh:bugfix/onChangeListenTo

Conversation

@Pascalmh
Copy link
Copy Markdown
Contributor

@Pascalmh Pascalmh commented May 4, 2026

🎯 Changes

ToDo:

  • fixes a bug where isValidating remians true when multiple fields are set simultaniasly in while using onChangeListenTo on multiple fields and running onChangeAsync checks

✅ 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

  • Tests
    • Added a Vitest regression test simulating rapid/autofill updates across linked fields to ensure async validation runs only once and clears correctly.
  • Bug Fixes
    • Introduced per-field validation counting so "validating" flags are set/cleared only for fields with async validators, preventing stuck validating states and restoring accurate form validity and submitability.
  • Chores
    • Added a changeset to publish a patch.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Async validation now tracks per-field active async runs with a counter, isolating a field’s own async validators from linked fields’ validators. Initialization and defaults include the counter; tests simulate rapid setValue calls to ensure no linked field remains stuck validating.

Changes

Per-field async validation counter and isolated linked-field tracking

Layer / File(s) Summary
Data Shape
packages/form-core/src/metaHelper.ts, packages/form-core/src/FieldApi.ts, packages/form-core/src/FormApi.ts
Adds validationCount: number to defaultFieldMeta, FieldMetaBase, and initializes validationCount: 0 during form/field meta setup.
Core Implementation
packages/form-core/src/FieldApi.ts
Introduces startValidation() / endValidation() to increment/decrement validationCount and toggle isValidating only on transitions; validateAsync computes hasAsyncValidators from the current field's validators and derives linkedFieldsWithAsyncValidators.
Wiring / State Toggles
packages/form-core/src/FieldApi.ts, packages/form-core/src/FormApi.ts
Only fields (self or linked) that actually have async validate are started/stopped via the new counter-based helpers; other linked fields are no longer toggled. Form-level meta initialization now sets the counter.
Tests
packages/form-core/tests/FormApi.spec.ts
Adds a Vitest that simulates rapid setValue calls across street and its onChangeListenTo linked fields to verify the async validator runs once and no isValidating flags or form validity flags remain stuck. Uses fake timers.
Release Note
.changeset/true-impalas-behave.md
Adds a patch changeset describing a fix for a race condition in onChangeListenTo when listening to multiple fields.

Sequence Diagram(s)

sequenceDiagram
    participant UI as rgba(66,133,244,0.5) UI
    participant Field as rgba(219,68,55,0.5) FieldApi (street)
    participant Linked as rgba(15,157,88,0.5) Linked FieldApis (houseNo, zipCode, city)
    participant Validator as rgba(142,36,170,0.5) Async Validator
    participant Form as rgba(255,193,7,0.5) Form State

    UI->>Field: rapid setValue calls (simulate autofill)
    Field->>Field: determine hasAsyncValidators (self) and linkedFieldsWithAsyncValidators
    Field->>Field: startValidation() for self if needed
    Field->>Linked: startValidation() only for linkedFieldsWithAsyncValidators
    Field->>Validator: invoke debounced async validation
    Validator-->>Field: return result
    Field->>Field: endValidation() for self if finished
    Field->>Linked: endValidation() for linkedFieldsWithAsyncValidators
    Field->>Form: update isFieldsValidating and validity flags
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through fields with counters bright,

One validation call, and all was right.
No flags left hanging, no phantom race,
Quiet forms now dwell in gentle grace. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly describes the main fix: resolving a race condition in FieldApi when using onChangeListenTo.
Description check ✅ Passed PR description provides the core fix summary and confirms all required checklist items are completed, including a changeset.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 4, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit 4233b45

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

☁️ Nx Cloud last updated this comment at 2026-05-05 08:09:47 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 4, 2026

More templates

@tanstack/angular-form

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

@tanstack/form-core

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

@tanstack/form-devtools

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

@tanstack/lit-form

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

@tanstack/react-form

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

@tanstack/react-form-devtools

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

@tanstack/react-form-nextjs

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

@tanstack/react-form-remix

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

@tanstack/react-form-start

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

@tanstack/solid-form

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

@tanstack/solid-form-devtools

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

@tanstack/svelte-form

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

@tanstack/vue-form

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

commit: 5b0ab58

@sentry
Copy link
Copy Markdown

sentry Bot commented May 4, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.27%. Comparing base (6892ed0) to head (8551da0).
⚠️ Report is 171 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2143      +/-   ##
==========================================
- Coverage   90.35%   90.27%   -0.08%     
==========================================
  Files          38       49      +11     
  Lines        1752     2047     +295     
  Branches      444      533      +89     
==========================================
+ Hits         1583     1848     +265     
- Misses        149      179      +30     
  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.

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.

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

1868-1872: ⚡ Quick win

isValidating is set/cleared on all linked fields, including those with no async validators.

hasLinkedAsyncValidators gates the loop on any linked field having an async validator, but the loop body then marks every field in linkedFields. A linked field that only has sync validators (or no validators) gets a spurious isValidating: true → false flicker, which can cause unnecessary UI loading-indicator renders.

Because each linkedFieldValidates entry already carries a .field reference (attached at line 1836), the target set is cheap to compute.

♻️ Proposed refactor — iterate only over fields that have actual async validators
+    const linkedFieldsWithAsyncValidators = new Set(
+      linkedFieldValidates.filter((v) => v.validate).map((v) => v.field),
+    )
     const hasLinkedAsyncValidators = linkedFieldValidates.some(
       (v) => v.validate,
     )

     if (hasLinkedAsyncValidators) {
-      for (const linkedField of linkedFields) {
+      for (const linkedField of linkedFieldsWithAsyncValidators) {
         linkedField.setMeta((prev) => ({ ...prev, isValidating: true }))
       }
     }
     if (hasLinkedAsyncValidators) {
-      for (const linkedField of linkedFields) {
+      for (const linkedField of linkedFieldsWithAsyncValidators) {
         linkedField.setMeta((prev) => ({ ...prev, isValidating: false }))
       }
     }

Also applies to: 1988-1992

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

In `@packages/form-core/src/FieldApi.ts` around lines 1868 - 1872,
hasLinkedAsyncValidators currently gates updating linkedFields but then sets
isValidating on every linkedField; change the loops so they only iterate over
the actual fields that have async validators by using the linkedFieldValidates
entries (each contains a .field) instead of linkedFields. Specifically, replace
the for (const linkedField of linkedFields) { linkedField.setMeta(prev =>
({...prev, isValidating: true})) } with a loop over linkedFieldValidates.map(e
=> e.field) (or filter linkedFields by presence in linkedFieldValidates) and do
the same change for the corresponding clearing loop (the block around the other
occurrence noted at ~1988-1992) so only fields with async validators receive the
isValidating updates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/form-core/src/FieldApi.ts`:
- Around line 1868-1872: hasLinkedAsyncValidators currently gates updating
linkedFields but then sets isValidating on every linkedField; change the loops
so they only iterate over the actual fields that have async validators by using
the linkedFieldValidates entries (each contains a .field) instead of
linkedFields. Specifically, replace the for (const linkedField of linkedFields)
{ linkedField.setMeta(prev => ({...prev, isValidating: true})) } with a loop
over linkedFieldValidates.map(e => e.field) (or filter linkedFields by presence
in linkedFieldValidates) and do the same change for the corresponding clearing
loop (the block around the other occurrence noted at ~1988-1992) so only fields
with async validators receive the isValidating updates.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 79e4efed-e8e0-40a1-b9f4-da213214b504

📥 Commits

Reviewing files that changed from the base of the PR and between 4822f00 and aaf5a95.

📒 Files selected for processing (2)
  • packages/form-core/src/FieldApi.ts
  • packages/form-core/tests/FormApi.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/form-core/tests/FormApi.spec.ts

@Pascalmh Pascalmh force-pushed the bugfix/onChangeListenTo branch from aaf5a95 to ea9d5fb Compare May 5, 2026 07:14
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 (1)
packages/form-core/src/FieldApi.ts (1)

1858-1866: ⚡ Quick win

Simplify linkedFieldsWithAsyncValidators — the .some() guard is redundant.

The ternary short-circuits on .some() to avoid allocating an empty array, but filter(...).map(...) on a collection that produces no results already returns []. The current code iterates linkedFieldValidates twice unnecessarily.

♻️ Proposed simplification
-    const linkedFieldsWithAsyncValidators = linkedFieldValidates.some(
-      (v) => v.validate,
-    )
-      ? Array.from(
-          new Set(
-            linkedFieldValidates.filter((v) => v.validate).map((v) => v.field),
-          ),
-        )
-      : []
+    const linkedFieldsWithAsyncValidators = Array.from(
+      new Set(
+        linkedFieldValidates.filter((v) => v.validate).map((v) => v.field),
+      ),
+    )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/form-core/src/FieldApi.ts` around lines 1858 - 1866, The current
computation of linkedFieldsWithAsyncValidators redundantly calls
linkedFieldValidates twice by using .some() as a guard; replace that ternary
with a single expression that always builds the unique list (e.g.,
Array.from(new Set(linkedFieldValidates.filter(v => v.validate).map(v =>
v.field)))) so you only iterate linkedFieldValidates once and get [] when there
are no matches; update the assignment where linkedFieldsWithAsyncValidators is
declared in FieldApi.ts accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/form-core/src/FieldApi.ts`:
- Around line 1874-1876: The linked-field race occurs because validateAsync for
a field clears another field's meta.isValidating when its own linked validations
finish, even if that other field has its own in-flight async validation; fix by
tracking in-flight validation owners per field (e.g., add a Map/Set like
validatingOwners or a numeric validateCount stored on field meta) and update
validateAsync and linked validation paths to increment the owner before marking
isValidating true and decrement on completion/abort, only setting
meta.isValidating = false when the owner count/Set for that field reaches zero;
update the code paths around linkedFieldsWithAsyncValidators, linkedPromises
resolution, the abort handling, and the blocks that currently call
linkedField.setMeta(... isValidating: false ...) so they use the new owner-aware
logic (reference symbols: validateAsync, linkedFieldsWithAsyncValidators,
linkedPromises, setMeta, isValidating, and the computed
form.state.isFieldsValidating).

---

Nitpick comments:
In `@packages/form-core/src/FieldApi.ts`:
- Around line 1858-1866: The current computation of
linkedFieldsWithAsyncValidators redundantly calls linkedFieldValidates twice by
using .some() as a guard; replace that ternary with a single expression that
always builds the unique list (e.g., Array.from(new
Set(linkedFieldValidates.filter(v => v.validate).map(v => v.field)))) so you
only iterate linkedFieldValidates once and get [] when there are no matches;
update the assignment where linkedFieldsWithAsyncValidators is declared in
FieldApi.ts accordingly.
🪄 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: 3a119c7c-c7af-4b83-b380-5e47d3f11a67

📥 Commits

Reviewing files that changed from the base of the PR and between aaf5a95 and 1e22a04.

📒 Files selected for processing (2)
  • packages/form-core/src/FieldApi.ts
  • packages/form-core/tests/FormApi.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/form-core/tests/FormApi.spec.ts

Comment thread packages/form-core/src/FieldApi.ts
@Pascalmh Pascalmh force-pushed the bugfix/onChangeListenTo branch from 1e22a04 to 8551da0 Compare May 5, 2026 07:37
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.

🧹 Nitpick comments (1)
packages/form-core/tests/FormApi.spec.ts (1)

2995-2995: ⚡ Quick win

vi.useRealTimers() should be in an afterEach (or try/finally) rather than inline

As written, if vi.runAllTimersAsync() on line 3039 throws unexpectedly, the vi.useRealTimers() on line 3053 is never reached, leaving fake timers active for subsequent tests. Because all assertions are expect.soft (non-throwing), this risk is limited to an exception from vi.runAllTimersAsync() itself — but that's a real, non-trivial surface area.

The conventional fix is either:

♻️ Option A — `afterEach` (preferred, aligns with Vitest idioms)
+  afterEach(() => {
+    vi.useRealTimers()
+  })
+
   it('should not leave linked fields stuck in isValidating when multiple setValue calls trigger concurrent async validation', async () => {
     vi.useFakeTimers()
     // ...
-    vi.useRealTimers()
   })
♻️ Option B — `try/finally` (if a scoped `afterEach` is not desirable)
   it('should not leave linked fields stuck in isValidating ...', async () => {
     vi.useFakeTimers()
+    try {
       // ... body ...
       await vi.runAllTimersAsync()
       expect.soft(validationFn).toHaveBeenCalledTimes(1)
       // ...
-    vi.useRealTimers()
+    } finally {
+      vi.useRealTimers()
+    }
   })

Also applies to: 3053-3053

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/form-core/tests/FormApi.spec.ts` at line 2995, The test enables fake
timers with vi.useFakeTimers() but restores them inline with vi.useRealTimers(),
risking leaked fake timers if vi.runAllTimersAsync() throws; update the test
around vi.useFakeTimers()/vi.runAllTimersAsync()/vi.useRealTimers() to guarantee
restoration by either moving vi.useRealTimers() into an afterEach hook that
always runs, or wrap the block in a try/finally (call vi.useRealTimers() in
finally) so vi.useRealTimers() is always executed; search for
vi.useFakeTimers(), vi.runAllTimersAsync(), and vi.useRealTimers() in the test
to apply the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/form-core/tests/FormApi.spec.ts`:
- Line 2995: The test enables fake timers with vi.useFakeTimers() but restores
them inline with vi.useRealTimers(), risking leaked fake timers if
vi.runAllTimersAsync() throws; update the test around
vi.useFakeTimers()/vi.runAllTimersAsync()/vi.useRealTimers() to guarantee
restoration by either moving vi.useRealTimers() into an afterEach hook that
always runs, or wrap the block in a try/finally (call vi.useRealTimers() in
finally) so vi.useRealTimers() is always executed; search for
vi.useFakeTimers(), vi.runAllTimersAsync(), and vi.useRealTimers() in the test
to apply the fix.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 55fa2bb8-fff8-457f-9d70-f86264a0e5f0

📥 Commits

Reviewing files that changed from the base of the PR and between 1e22a04 and 8551da0.

📒 Files selected for processing (3)
  • .changeset/true-impalas-behave.md
  • packages/form-core/src/FieldApi.ts
  • packages/form-core/tests/FormApi.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/true-impalas-behave.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/form-core/src/FieldApi.ts

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 (1)
packages/form-core/src/FieldApi.ts (1)

1906-1912: ⚡ Quick win

Batch the per-field startValidation / endValidation calls to avoid N+1 store updates.

Each startValidation / endValidation call goes through setMetaform.setFieldMetabaseStore.setState, which triggers fieldMetaDerived and store recomputation. With several linked fields, the loops at lines 1910–1912 and 2028–2030 trigger one recomputation per field per phase. Wrapping each phase in batch() (already imported on line 1) collapses them into a single store update.

♻️ Proposed batching
-    if (hasAsyncValidators) {
-      this.startValidation()
-    }
-
-    for (const linkedField of linkedFieldsWithAsyncValidators) {
-      linkedField.startValidation()
-    }
+    batch(() => {
+      if (hasAsyncValidators) {
+        this.startValidation()
+      }
+      for (const linkedField of linkedFieldsWithAsyncValidators) {
+        linkedField.startValidation()
+      }
+    })
-    // Only reset isValidating if we set it to true earlier
-    if (hasAsyncValidators) {
-      this.endValidation()
-    }
-
-    for (const linkedField of linkedFieldsWithAsyncValidators) {
-      linkedField.endValidation()
-    }
+    batch(() => {
+      // Only reset isValidating if we set it to true earlier
+      if (hasAsyncValidators) {
+        this.endValidation()
+      }
+      for (const linkedField of linkedFieldsWithAsyncValidators) {
+        linkedField.endValidation()
+      }
+    })

Also applies to: 2024-2030

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/form-core/src/FieldApi.ts` around lines 1906 - 1912, The per-field
calls to startValidation and endValidation cause N+1 store updates; wrap the
blocks that call this.startValidation() (guarded by hasAsyncValidators) and the
for-loop over linkedFieldsWithAsyncValidators, and likewise the matching
endValidation block (the loops that call this.endValidation() and
linkedField.endValidation()), inside batch(...) so the multiple setMeta →
form.setFieldMeta → baseStore.setState updates collapse into a single batched
update; locate the calls by the symbols startValidation, endValidation, and
linkedFieldsWithAsyncValidators and replace the separate calls with a single
batch call that executes them all.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/form-core/src/FieldApi.ts`:
- Around line 1800-1830: endValidation unconditionally decrements
validationCount causing it to go negative and can leave isValidating stuck;
change endValidation (and post-Promise.all callers) to defensively no-op when
prev.validationCount === 0 (or clamp newCount = Math.max(0, prev.validationCount
- 1)) and only set isValidating to false when newCount === 0 and
prev.isValidating is true; additionally short-circuit stale validation
completions by checking the field instance (mirror the existing
getInfo().instance !== field guard) before calling endValidation so
unmount/reset paths (unmount, form.resetField, setMeta(defaultFieldMeta)) cannot
mutate a freshly reset meta.

---

Nitpick comments:
In `@packages/form-core/src/FieldApi.ts`:
- Around line 1906-1912: The per-field calls to startValidation and
endValidation cause N+1 store updates; wrap the blocks that call
this.startValidation() (guarded by hasAsyncValidators) and the for-loop over
linkedFieldsWithAsyncValidators, and likewise the matching endValidation block
(the loops that call this.endValidation() and linkedField.endValidation()),
inside batch(...) so the multiple setMeta → form.setFieldMeta →
baseStore.setState updates collapse into a single batched update; locate the
calls by the symbols startValidation, endValidation, and
linkedFieldsWithAsyncValidators and replace the separate calls with a single
batch call that executes them all.
🪄 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: b4e4f882-5129-486d-971d-16ddd69b3c47

📥 Commits

Reviewing files that changed from the base of the PR and between 8551da0 and 5f7e2ca.

📒 Files selected for processing (3)
  • packages/form-core/src/FieldApi.ts
  • packages/form-core/src/FormApi.ts
  • packages/form-core/src/metaHelper.ts

Comment thread packages/form-core/src/FieldApi.ts
@Pascalmh Pascalmh force-pushed the bugfix/onChangeListenTo branch from 5f7e2ca to c0fd89f Compare May 5, 2026 08:04
autofix-ci Bot and others added 2 commits May 5, 2026 08:05
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@Pascalmh Pascalmh changed the title test(FieldApi): add test for resolving isValidating state during concurrent async validation fix(FieldApi): fix race condition when using onChangeListenTo May 5, 2026
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