Skip to content

fix: deepkeyandvalue behavior in union types#2057

Open
mdm317 wants to merge 8 commits into
TanStack:mainfrom
mdm317:fix/union-types-in-deepkeysandvalues
Open

fix: deepkeyandvalue behavior in union types#2057
mdm317 wants to merge 8 commits into
TanStack:mainfrom
mdm317:fix/union-types-in-deepkeysandvalues

Conversation

@mdm317
Copy link
Copy Markdown
Contributor

@mdm317 mdm317 commented Mar 3, 2026

🎯 Changes

fixes #1813

Fix DeepKeysAndValues to return nullish values instead of never when the value type is nullish

  • main
type Test = DeepKeysAndValues<{ id : undefined }>
//        ^? never

ts playground

  • this branch
type Test = DeepKeysAndValues<{ id : undefined }>
//        ^? { key : 'id', value: undefined}

Due to the above change, I replaced DeepKeysOfType with DeepKeysOfNonNullableType in the group API.

#2057 (comment)

✅ 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).

Change

Summary by CodeRabbit

  • Bug Fixes

    • Fixed type behavior so unions keep nullish members where appropriate and field-group selections reject paths that are nullish-only.
  • Tests

    • Added type-level tests covering nullish preservation, tuple/array index cases, discriminated-union nulls, and rejection of nullish-only field-group paths.
  • Chores

    • Bumped form-related packages with a patch changeset.

Review Change Stack

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 3, 2026

🦋 Changeset detected

Latest commit: 81806f8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@tanstack/react-form Patch
@tanstack/solid-form Patch
@tanstack/form-core Patch
@tanstack/react-form-nextjs Patch
@tanstack/react-form-remix Patch
@tanstack/react-form-start Patch
@tanstack/angular-form Patch
@tanstack/form-devtools Patch
@tanstack/lit-form Patch
@tanstack/svelte-form Patch
@tanstack/vue-form Patch
@tanstack/react-form-devtools Patch
@tanstack/solid-form-devtools Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment on lines +201 to +202
? [NonNullable<V>] extends [never]
? never
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Since the previous behavior inferred never when the value was nullish,
the changes were updated to continue inferring never for nullish value types.

Comment on lines +208 to +209
export type DeepKeysOfNonNullableType<TData, TValue> =
ExtractByNonNullableValue<DeepKeysAndValues<TData>, TValue>['key']
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

After changing DeepKeysAndValues to include nullish values, a bug was introduced in the group API when using DeepKeysOfType.

When the value type is nullish, it previously evaluated as:

Extract<never, nullish | TValue>

However, after the change, it became:

Extract<nullish, nullish | TValue>

which now returns nullish.
To resolve this issue, I implemented a new utility type to correctly infer TFields in the group API.

@crutchcorn
Copy link
Copy Markdown
Member

The code looks good to me and makes sense, but given that @LeCarbonator has to remind me every week how FieldGroup actually works, I'm going to pass it to him 😅

Pinged internally so response time is usually quicker that way.

Copy link
Copy Markdown
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.

Looks interesting! Reported cause and the reasoning for the change make sense to me! As far as the changes go, there's only some regression tests missing as well as an instantiation check. Refer to the two open threads for more details.

Thanks for tackling this!

? TAcc | UnknownDeepKeyAndValue<TParent>
: T extends object
? DeepKeyAndValueObject<TParent, T, TAcc>
: TAcc
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This particular change addresses not just DeepKeysOfType, but also DeepKeys and DeepValue.

I see one test was adjusted to acknowledge this, but some additional LOC for DeepKeys and DeepValue would be fantastic. Not merely for this PR, but also for future typings to follow the spec.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for review!
I added case in this commit 2ee891c

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How's type instantiation treating this type change? Have you tested them before / after? Did they explode or is it only a bit more?

I would be surprised if it did, but you never know with some of those seemingly simple changes.

If you haven't already, I can help out with testing it. Just let me know.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Measured with the following command.
Instantiations increased slightly on this branch's HEAD c0d762dd compared to base 3886dcc5.

npx tsc --extendedDiagnostics

packages/form-core

metric base HEAD
Types 84,335 86,803
Instantiations 436,577 449,014

packages/react-form

metric base HEAD
Types 58,924 60,824
Instantiations 435,282 447,536

packages/preact-form

metric base HEAD
Types 51,525 52,567
Instantiations 322,435 326,877

packages/vue-form

metric base HEAD
Types 25,531 26,099
Instantiations 113,263 114,659

packages/solid-form

metric base HEAD
Types 37,855 39,301
Instantiations 203,009 210,621

Copy link
Copy Markdown
Contributor

@LeCarbonator LeCarbonator May 18, 2026

Choose a reason for hiding this comment

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

That's only a minor amount because this would only account for a few unit tests.

I took the liberty to give it a try in my work space. We use TanStack Form a lot there and we have way more types, so the impact should be a bit clearer.

packages/react-form

metric v1.28 HEAD Diff (~)
Types 439'839 439'089 - 0.17%
Instantiations 2'983'698 2'978'088 - 0.18%
Memory 1'406'720K 1'412'563K + 0.41%

Looks clean!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for checking — appreciate it.
Let me know if there’s anything else I should work on.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 16, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4393308-205f-47a2-8c4e-d9f0a5c16663

📥 Commits

Reviewing files that changed from the base of the PR and between c0d762d and 7bcae35.

📒 Files selected for processing (2)
  • packages/form-core/tests/util-types.test-d.ts
  • packages/react-form/tests/createFormHook.test-d.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/react-form/tests/createFormHook.test-d.tsx
  • packages/form-core/tests/util-types.test-d.ts

📝 Walkthrough

Walkthrough

Adds a never-base case to deep-key recursion, introduces DeepKeysOfNonNullableType to filter deep-key paths by non-nullable accessor values, updates field-group generic constraints across form-core/react/solid/preact to use it, and adds type tests plus a changeset documenting the patch releases.

Changes

Union Type Handling in Field Groups

Layer / File(s) Summary
Core deep-key type utilities
packages/form-core/src/util-types.ts
DeepKeysAndValuesImpl short-circuits on never; new DeepKeysOfNonNullableType exported type filters deep key paths by matching NonNullable accessor values.
Field group core API changes
packages/form-core/src/FieldGroupApi.ts
FieldGroupApi and FieldGroupOptions switch TFields constraints from `DeepKeysOfType<..., TFieldGroupData
Form hook imports and withFieldGroup adapters
packages/react-form/src/createFormHook.tsx, packages/solid-form/src/createFormHook.tsx, packages/preact-form/src/createFormHook.tsx
Adapter files now import DeepKeysOfNonNullableType and use it in withFieldGroup return typings.
useFieldGroup and field-group signature updates
packages/react-form/src/useFieldGroup.tsx, packages/preact-form/src/useFieldGroup.tsx
useFieldGroup and AppFieldExtended field-group TFields constraints updated to DeepKeysOfNonNullableType<TFormData, TFieldGroupData>.
Solid createFieldGroup updates
packages/solid-form/src/createFieldGroup.tsx
Solid adapter replaces DeepKeysOfType imports and updates createFieldGroup/AppFieldExtendedSolidFieldGroupApi constraints to DeepKeysOfNonNullableType.
Type-level validation tests
packages/form-core/tests/util-types.test-d.ts, packages/form-core/tests/FieldGroupApi.test-d.ts, packages/react-form/tests/createFormHook.test-d.tsx, packages/solid-form/tests/createFormHook.test-d.tsx
New tests validate nullish-preservation in DeepKeys/DeepValue and assert that nullish-only field-group paths are rejected using @ts-expect-error.
Release metadata
.changeset/blue-geese-kneel.md
Adds a changeset declaring patch bumps for form packages and notes the DeepKeysAndValues fix preserving nullish types in union cases.

🎯 3 (Moderate) | ⏱️ ~25 minutes

"🐰 I hopped through keys and unions wide,
I nudged out never from the type's inside.
NonNullable paths now pass the test,
Nullish friends are kept, not suppressed.
Hooray — your field groups type their best!"

🚥 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
Title check ✅ Passed The PR title clearly describes the main change: fixing DeepKeysAndValues behavior to properly handle union types with nullish values.
Description check ✅ Passed The PR description follows the template structure with proper sections (🎯 Changes, ✅ Checklist, 🚀 Release Impact), includes linked issue reference, explains the fix with before/after examples, and confirms completion of required checklist items.
Linked Issues check ✅ Passed The PR addresses all objectives from issue #1813: fixes DeepKeysAndValues to return nullish values instead of never, preserves union types, introduces DeepKeysOfNonNullableType for field-group APIs, and includes comprehensive type tests validating the changes.
Out of Scope Changes check ✅ Passed All changes are scoped to the objectives: core type logic updates in util-types, type constraint updates in form adapters (react/solid/preact), corresponding type tests, and a changeset. No unrelated modifications detected.

✏️ 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.

TFieldGroupData,
TFields extends
| DeepKeysOfType<TFormData, TFieldGroupData | null | undefined>
| DeepKeysOfNonNullableType<TFormData, TFieldGroupData>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Apply the same non-nullable field group fix to preact-form.

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented May 18, 2026

View your CI Pipeline Execution ↗ for commit c0d762d

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

☁️ Nx Cloud last updated this comment at 2026-05-18 15:18:34 UTC

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 18, 2026

More templates

@tanstack/angular-form

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

@tanstack/form-core

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

@tanstack/form-devtools

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

@tanstack/lit-form

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

@tanstack/preact-form

npm i https://pkg.pr.new/@tanstack/preact-form@2057

@tanstack/react-form

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

@tanstack/react-form-devtools

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

@tanstack/react-form-nextjs

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

@tanstack/react-form-remix

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

@tanstack/react-form-start

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

@tanstack/solid-form

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

@tanstack/solid-form-devtools

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

@tanstack/svelte-form

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

@tanstack/vue-form

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

commit: 7bcae35

@codecov-commenter
Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.27%. Comparing base (6892ed0) to head (7bcae35).
⚠️ Report is 217 commits behind head on main.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2057      +/-   ##
==========================================
+ Coverage   90.35%   91.27%   +0.91%     
==========================================
  Files          38       59      +21     
  Lines        1752     2314     +562     
  Branches      444      569     +125     
==========================================
+ Hits         1583     2112     +529     
- 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.

@mdm317 mdm317 requested a review from LeCarbonator May 22, 2026 06:44
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.

Union types are not handled correctly

4 participants