fix(form-core): fix validator type inference in formOptions#2198
fix(form-core): fix validator type inference in formOptions#2198Dharya-dev wants to merge 1 commit into
Conversation
When using formOptions() with validators, the callback parameter types were inferred as 'any' instead of the expected typed shape (e.g. data.value was any instead of TFormData). Root cause: the return type 'TOptions' captured the raw object literal type before TypeScript could resolve the validator generics through FormOptions. Fix: use Omit+Pick to selectively re-type the validator-dependent properties (validators, onSubmit, onSubmitInvalid, listeners) with properly resolved generic types wrapped in NoInfer, while keeping TOptions for all other properties to preserve spread/override compatibility. Added type-level tests that verify: - validators.onSubmit data.value is typed correctly (not any) - onSubmit handler data.value is typed correctly (not any) - spreading formOpts into FormApi still works - overriding validators when spreading still works Closes TanStack#1613
📝 WalkthroughWalkthroughThis PR fixes a TypeScript type inference bug in ChangesValidator callback type inference in formOptions
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/formOptions.ts`:
- Around line 81-100: The return type currently reintroduces dependent keys via
Pick<Partial<FormOptions<...>>, FormOptionsDependentKeys & keyof TOptions>,
which forces those keys to be optional; replace that Partial+Pick overlay with a
mapped type that preserves the original optionality from TOptions while mapping
each key to the resolved callback type from FormOptions. Concretely, instead of
Pick<Partial<...>, ...>, create a mapped overlay like: for each K in
FormOptionsDependentKeys & keyof TOptions produce a property K whose type is the
corresponding resolved FormOptions[K] but marked optional only when TOptions has
K as optional (use a conditional check like "undefined extends TOptions[K]" to
decide whether to append | undefined or make the property optional), and reuse
the existing FormOptions<...> instantiation to derive the resolved callback
types; update the signature in formOptions to use this new mapped overlay so
presence/optionality from TOptions is preserved.
🪄 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: b5cf28d1-a950-4b8d-b1f5-6ed4306220dc
📒 Files selected for processing (3)
.changeset/fix-form-options-validator-inference.mdpackages/form-core/src/formOptions.tspackages/form-core/tests/formOptions.test-d.ts
| ): Omit<TOptions, FormOptionsDependentKeys> & | ||
| Pick< | ||
| Partial< | ||
| FormOptions< | ||
| NoInfer<TFormData>, | ||
| NoInfer<TOnMount>, | ||
| NoInfer<TOnChange>, | ||
| NoInfer<TOnChangeAsync>, | ||
| NoInfer<TOnBlur>, | ||
| NoInfer<TOnBlurAsync>, | ||
| NoInfer<TOnSubmit>, | ||
| NoInfer<TOnSubmitAsync>, | ||
| NoInfer<TOnDynamic>, | ||
| NoInfer<TOnDynamicAsync>, | ||
| NoInfer<TOnServer>, | ||
| NoInfer<TSubmitMeta> | ||
| > | ||
| >, | ||
| FormOptionsDependentKeys & keyof TOptions | ||
| > { |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In TypeScript, does Pick<Partial<{ onSubmit: () => void }>, "onSubmit">keeponSubmitoptional, and does{ onSubmit?: () => void }["onSubmit"]includeundefined?
💡 Result:
Yes on both points. 1) Pick<Partial<{ onSubmit: => void }>, "onSubmit"> keeps onSubmit optional. - Partial makes every property optional: it “set[s] … properties … to optional” [1]. - Pick preserves whether a property is optional in the source type because it “picks” those named properties from the given type [1]. 2) { onSubmit?: => void }["onSubmit"] includes undefined. - An optional property onSubmit?: ... is treated as having the possibility of being absent, so indexing its property type yields a union with undefined (optional implies “possibly undefined”). Practical equivalence: - Pick<Partial<{ onSubmit: => void }>, "onSubmit"> behaves like { onSubmit?: => void }. - Therefore, ({ onSubmit?: => void })["onSubmit"] is ( => void) | undefined.
Citations:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="packages/form-core/src/formOptions.ts"
# Show the relevant section around the reported lines
sed -n '1,140p' "$FILE" | cat -n
# Locate FormOptionsDependentKeys definition and any related helpers/usages
rg -n "FormOptionsDependentKeys" "$FILE"Repository: TanStack/form
Length of output: 3988
Preserve presence/optionality of dependent callback keys in formOptions return type
formOptions reintroduces validators/onSubmit/onSubmitInvalid/listeners via Pick<Partial<FormOptions<...>>, ...>, and Partial makes those keys optional—so even when TOptions supplies them, the return type can widen them to ... | undefined (e.g. onSubmit?: ...).
Replace the Pick<Partial<...>> overlay with a mapped overlay that preserves optionality from TOptions while using the resolved callback types.
🤖 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/formOptions.ts` around lines 81 - 100, The return type
currently reintroduces dependent keys via Pick<Partial<FormOptions<...>>,
FormOptionsDependentKeys & keyof TOptions>, which forces those keys to be
optional; replace that Partial+Pick overlay with a mapped type that preserves
the original optionality from TOptions while mapping each key to the resolved
callback type from FormOptions. Concretely, instead of Pick<Partial<...>, ...>,
create a mapped overlay like: for each K in FormOptionsDependentKeys & keyof
TOptions produce a property K whose type is the corresponding resolved
FormOptions[K] but marked optional only when TOptions has K as optional (use a
conditional check like "undefined extends TOptions[K]" to decide whether to
append | undefined or make the property optional), and reuse the existing
FormOptions<...> instantiation to derive the resolved callback types; update the
signature in formOptions to use this new mapped overlay so presence/optionality
from TOptions is preserved.
🎯 Changes
Fixes
formOptions()validator callback parameters being typed asanyinstead of the correctly inferred form data shape.Before (broken):
After (fixed):
Root Cause
The return type of
formOptions()wasTOptions, which captured the raw object literal type before TypeScript could resolve the validator generics throughFormOptions. This meant callback parameter types (likedatainonSubmit: (data) => ...) lost their connection toTFormData.Fix
Use
Omit+Pickto selectively re-type the validator-dependent properties (validators,onSubmit,onSubmitInvalid,listeners) with properly resolved generic types wrapped inNoInfer, while keepingTOptionsfor all other properties to preserve spread/override compatibility.This approach avoids the "excessively deep" type instantiation errors that occur when intersecting the full
FormOptionstype (due toin outvariance annotations).What's been tested
validators.onSubmitdata.value is typed correctly (notany)onSubmithandler data.value is typed correctly (notany)formOptsintoFormApistill worksCloses #1613
✅ Checklist
pnpm test:pr.🚀 Release Impact
Summary by CodeRabbit
any, improving type safety and developer experience.