Skip to content

fix(form-core): prevent removeValue from auto-touching shifted siblings#2133

Open
mixelburg wants to merge 1 commit intoTanStack:mainfrom
mixelburg:fix/removevalue-touched-siblings
Open

fix(form-core): prevent removeValue from auto-touching shifted siblings#2133
mixelburg wants to merge 1 commit intoTanStack:mainfrom
mixelburg:fix/removevalue-touched-siblings

Conversation

@mixelburg
Copy link
Copy Markdown

@mixelburg mixelburg commented Apr 18, 2026

What changed

In validateArrayFieldsStartingFrom, fields with an existing instance now call fieldInstance.validate(cause) directly instead of going through this.validateField(). This avoids the auto-touch side-effect in validateField that marks fields as touched even when the user never interacted with them.

Why

Calling form.removeValue(index) on an array field was flipping isTouched to true on every sibling at index ≥ removed index. The bug lives in validateField which auto-touches any field that has an instance but isn't yet touched.

How to reproduce

  1. Create a form with an array field
  2. Call form.removeValue(0) without interacting with other fields
  3. Observe that isTouched becomes true on all shifted siblings

Closes #2131

Summary by CodeRabbit

Bug Fixes

  • Optimized array field validation scheduling for already-mounted fields, improving performance and consistency of validation behavior during array operations.

When calling form.removeValue(index) on an array field, isTouched was
incorrectly set to true on every sibling at index >= removed index,
even when the user never interacted with those fields.

The fix bypasses validateField for fields with existing instances and
calls fieldInstance.validate() directly, which preserves validation
without the auto-touch side-effect.

Fixes TanStack#2131
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 18, 2026

📝 Walkthrough

Walkthrough

The validateArrayFieldsStartingFrom method in FormApi.ts was modified to conditionally invoke field validation. For already-mounted field instances, it now calls the instance's validate() method directly instead of validateField(), which preserves validation while bypassing auto-touch logic that incorrectly marked shifted array fields as touched.

Changes

Cohort / File(s) Summary
Array Validation Logic
packages/form-core/src/FormApi.ts
Modified validateArrayFieldsStartingFrom to check for existing field instances and invoke fieldInstance.validate(cause) directly when available, bypassing validateField's touch-marking behavior during array field shifts.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 When array fields shift and slide,
No phantom touches on the ride,
Direct validation calls the way,
Keeps isTouched clean, hip hooray!
A rabbit's hop of logic bright,
Makes array removals feel just right. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: preventing removeValue from auto-touching shifted siblings by modifying validation behavior in the FormApi.
Description check ✅ Passed The pull request description clearly explains what changed, why it was changed, and how to reproduce the bug, aligning with the provided template structure.
Linked Issues check ✅ Passed The code changes directly address the proposed fix in issue #2131 by modifying validateArrayFieldsStartingFrom to bypass validateField's auto-touch logic for fields with existing instances.
Out of Scope Changes check ✅ Passed The changes are limited to FormApi.ts and directly target the specific issue: modifying validateArrayFieldsStartingFrom to prevent auto-touching. No unrelated or out-of-scope changes are present.
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

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

🤖 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 1608-1616: The current code calls fieldInstance.validate(cause)
which avoids auto-touching but relies on FieldApi.validate returning [] when
meta.isTouched is false, leaving shifted untouched siblings unvalidated; change
this by adding an internal "validate without touching" path: extend
validateField to accept an options flag (e.g., { touch?: boolean }) or add a new
internal method (e.g., validateFieldInternal/FieldApi.validateWithoutTouch) that
runs the same validation logic regardless of meta.isTouched but does not mutate
touch state, then replace the direct call to fieldInstance.validate(cause) with
a call to validateField(nestedField, cause, { touch: false }) or the new
internal method so shifted siblings revalidate without auto-touch side effects;
update FieldApi.validate to delegate to the shared internal validator to avoid
duplication.
🪄 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: d506b7d4-0546-43d1-a8b3-a5847867e52b

📥 Commits

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

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

Comment on lines +1608 to +1616
Promise.resolve().then(() => {
// If the field instance already exists, call validate directly
// to avoid auto-touching shifted siblings
const fieldInstance = this.fieldInfo[nestedField]?.instance
if (fieldInstance) {
return fieldInstance.validate(cause)
}
return this.validateField(nestedField, cause)
}),
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

Preserve validation when bypassing auto-touch.

Calling fieldInstance.validate(cause) directly skips validation for untouched fields because FieldApi.validate returns [] when meta.isTouched is false (packages/form-core/src/FieldApi.ts:1991-1997). This fixes the touched mutation, but shifted untouched siblings will no longer revalidate, leaving stale field errors possible after insert/remove/replace.

Consider adding an internal “validate without touching” path instead of calling FieldApi.validate as-is.

Possible direction
-            if (fieldInstance) {
-              return fieldInstance.validate(cause)
-            }
-            return this.validateField(nestedField, cause)
+            return this.validateField(nestedField, cause, {
+              touch: false,
+            })

This would require extending validateField to make the isTouched mutation optional while still running the same validation logic.

🤖 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 1608 - 1616, The current code
calls fieldInstance.validate(cause) which avoids auto-touching but relies on
FieldApi.validate returning [] when meta.isTouched is false, leaving shifted
untouched siblings unvalidated; change this by adding an internal "validate
without touching" path: extend validateField to accept an options flag (e.g., {
touch?: boolean }) or add a new internal method (e.g.,
validateFieldInternal/FieldApi.validateWithoutTouch) that runs the same
validation logic regardless of meta.isTouched but does not mutate touch state,
then replace the direct call to fieldInstance.validate(cause) with a call to
validateField(nestedField, cause, { touch: false }) or the new internal method
so shifted siblings revalidate without auto-touch side effects; update
FieldApi.validate to delegate to the shared internal validator to avoid
duplication.

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.

removeValue on array field incorrectly sets isTouched=true on all shifted siblings

1 participant