Skip to content

Conversation

@jiji-hoon96
Copy link
Contributor

@jiji-hoon96 jiji-hoon96 commented Dec 2, 2025

Adds support for onDynamicListenTo to enable cross-field dynamic validation.

Changes

  • Added onDynamicListenTo to FieldValidators interface
  • Implemented dynamic cause handling in getLinkedFields method
  • Added unit tests for both sync and async scenarios
// Password confirmation with cross-field validation
<form.Field
  name="confirmPassword"
  validators={{
    onDynamicListenTo: ['password'],  // ← Listen to password changes
    onDynamic: ({ value, fieldApi }) => {
      const password = fieldApi.form.getFieldValue('password')
      return value !== password ? 'Passwords must match' : undefined
    },
  }}
/>

Fixes #1698

- Add onDynamicListenTo to FieldValidators interface
- Implement dynamic cause handling in getLinkedFields method
- Add password/confirmPassword example demonstrating cross-field validation
@changeset-bot
Copy link

changeset-bot bot commented Dec 2, 2025

⚠️ No Changeset found

Latest commit: 5dbb044

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

@nx-cloud
Copy link

nx-cloud bot commented Dec 2, 2025

View your CI Pipeline Execution ↗ for commit 5dbb044

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ⏳ In Progress ... View ↗
nx run-many --target=build --exclude=examples/** ✅ Succeeded 34s View ↗

☁️ Nx Cloud last updated this comment at 2025-12-08 08:47:04 UTC

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 2, 2025

More templates

@tanstack/angular-form

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

@tanstack/form-core

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

@tanstack/form-devtools

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

@tanstack/lit-form

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

@tanstack/react-form

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

@tanstack/react-form-devtools

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

@tanstack/react-form-nextjs

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

@tanstack/react-form-remix

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

@tanstack/react-form-start

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

@tanstack/solid-form

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

@tanstack/solid-form-devtools

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

@tanstack/svelte-form

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

@tanstack/vue-form

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

commit: 89adf3a

@codecov
Copy link

codecov bot commented Dec 2, 2025

Codecov Report

❌ Patch coverage is 90.47619% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.64%. Comparing base (6892ed0) to head (89adf3a).
⚠️ Report is 88 commits behind head on main.

Files with missing lines Patch % Lines
packages/form-core/src/FieldApi.ts 94.44% 2 Missing ⚠️
packages/form-core/src/FieldGroupApi.ts 0.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1896      +/-   ##
==========================================
- Coverage   90.35%   89.64%   -0.71%     
==========================================
  Files          38       48      +10     
  Lines        1752     1951     +199     
  Branches      444      493      +49     
==========================================
+ Hits         1583     1749     +166     
- Misses        149      181      +32     
- Partials       20       21       +1     

☔ 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 and others added 4 commits December 3, 2025 10:20
- Add test for onDynamic cross-field validation with onDynamicListenTo
- Add test for onDynamicAsync cross-field validation with onDynamicListenTo
- Fix getLinkedFields to return validator cause for each linked field
- Add 'dynamic' case to defaultValidationLogic
- Clear dynamic errors when running change/blur validation
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.

I like the idea, but the implementation needs some adjustment. There's a chance the issues come from a core problem with onDynamic, but it's not clear from this diff alone.

Missing changes

There's also FieldGroupApi.getFormFieldOptions that needs to include onDynamicListenTo. Follow the onChangeListenTo / onBlurListenTo lines in there as reference. Addressed in 89adf3a

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if adding this to the simple example makes sense, since

  • It's not a simple implementation
  • It's unrelated to the other fields

Having linked fields as examples is a good idea though. Maybe it could be its own example? Perhaps it can be added to large-form instead. I'll leave it up to you.

Copy link
Contributor Author

@jiji-hoon96 jiji-hoon96 Dec 8, 2025

Choose a reason for hiding this comment

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

I believe it makes more sense to include it in large-form rather than as a separate example.
Linked field validation is a pattern commonly used in real-world scenarios, so it fits naturally within that example.
I'll move it to large-form. Is that okay with you?

Comment on lines 1369 to 1385
const dynamicErrKey = getErrorMapKey('dynamic')
if (
this.state.meta.errorMap[dynamicErrKey] &&
this.state.meta.errorSourceMap[dynamicErrKey] === 'field'
) {
this.setMeta((prev) => ({
...prev,
errorMap: {
...prev.errorMap,
[dynamicErrKey]: undefined,
},
errorSourceMap: {
...prev.errorSourceMap,
[dynamicErrKey]: undefined,
},
}))
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems out of place for setValue. Why does onDynamic need special behaviour in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right
I was trying to clear dynamic errors before change validation runs, but if validate('change') passes, it should naturally clear all errors anyway.

Comment on lines +201 to +207
case 'dynamic': {
// Run dynamic, server validation
return props.runValidation({
validators: [onDynamicValidator, onServerValidator],
form: props.form,
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I assumed the point of onDynamic was that it would be dictated by validationLogic. However, it now needs to be addressed in revalidateLogic. I feel like there's some underlying issue that should be fixed, but I'll need to look at it myself to know for sure.

Comment on lines 1966 to 1969
let resolve!: () => void
let promise = new Promise((r) => {
resolve = r as never
})
Copy link
Contributor

Choose a reason for hiding this comment

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

This on-demand resolving is very difficult to follow in this unit test due to how hacky it is.

I think an implementation with vi.useFakeTimers and a setTimeout + vi.awaitAllTimersAsync is way clearer in terms of code flow.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll refactor to use vi.useFakeTimers() with setTimeout + vi.awaitAllTimersAsync() as you suggested. Much clearer.

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.

onDynamicListenTo does not exist

2 participants