Skip to content

fix: deepkeyandvalue behavior in union types#2057

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

fix: deepkeyandvalue behavior in union types#2057
mdm317 wants to merge 7 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

    • Improved type handling so field groups preserve nullish members in unions and reject field-group paths that are nullish-only.
  • Tests

    • Added type-level tests covering nullish preservation, tuple/array index cases, 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

@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: 6e3c11d3-1f55-4f93-bcc4-70892ac83b80

📥 Commits

Reviewing files that changed from the base of the PR and between 2ee891c and c0d762d.

📒 Files selected for processing (2)
  • packages/preact-form/src/createFormHook.tsx
  • packages/preact-form/src/useFieldGroup.tsx

📝 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/solid-form/src/createFieldGroup.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 title directly matches the main change: fixing DeepKeysAndValues behavior to handle union types correctly.
Description check ✅ Passed The description covers all required sections with detailed context, includes linked issue reference, checklist items completed, and changeset confirmation.
Linked Issues check ✅ Passed All coding requirements from issue #1813 are addressed: DeepKeysAndValues now preserves nullish types, union types are handled correctly, and related APIs updated accordingly.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing union type handling in DeepKeysAndValues and propagating that fix through affected APIs.

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

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.

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

3 participants