Skip to content

Conversation

stefan-huck
Copy link
Contributor

No description provided.

Copy link

nx-cloud bot commented Jul 29, 2025

View your CI Pipeline Execution ↗ for commit d862d99

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

☁️ Nx Cloud last updated this comment at 2025-07-30 19:41:29 UTC

Copy link

pkg-pr-new bot commented Jul 29, 2025

Copy link

codecov bot commented Jul 29, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.38%. Comparing base (5dd89f5) to head (d862d99).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1639      +/-   ##
==========================================
+ Coverage   90.37%   90.38%   +0.01%     
==========================================
  Files          36       36              
  Lines        1621     1623       +2     
  Branches      385      386       +1     
==========================================
+ Hits         1465     1467       +2     
  Misses        139      139              
  Partials       17       17              

☔ 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.

@LeCarbonator
Copy link
Contributor

@stefan-huck Thanks for the report! I see the edge case, but how did you encounter this?

I'll add a unit test for the new line shortly, unless you want to add one. Definitely don't want to regress on this.

@LeCarbonator
Copy link
Contributor

  it('should not duplicate dots if the second path starts with one', () => {
    expect(concatenatePaths('foo', '.bar')).toBe('foo.bar')
  })

This should do the trick. No idea when this situation could happen though.

@stefan-huck
Copy link
Contributor Author

stefan-huck commented Jul 29, 2025

"use client"

import { createFormHook, createFormHookContexts } from "@tanstack/react-form"

const { fieldContext, formContext } = createFormHookContexts()

export const { useAppForm, withForm, withFieldGroup } = createFormHook({
  fieldComponents: {},
  formComponents: {},
  fieldContext,
  formContext,
})

const ChildForm = withFieldGroup({
  defaultValues: {} as {
    foo: string
    // The nested prop 'street' shares the same name as the parent prop street
    address: { street: string }
  },
  render: ({ group }) => (
    <group.AppForm>
      <group.AppField name="address.street">
        {field => {
          console.info(field.name, field.state.value)
          // customerAddress..street some-street-value (see double dots)
          return null
        }}
      </group.AppField>
    </group.AppForm>
  ),
})

export function ParentForm() {
  const form = useAppForm({
    defaultValues: {
      customerAddress: { street: "some-street-value" },
      foo: "bar",
    },
  })
  return (
    <form.AppForm>
      <ChildForm
        form={form}
        fields={{
          foo: "foo",
          address: "customerAddress",
        }}
      />
    </form.AppForm>
  )
}

I needed some time to dig into this, as it only occurs under strange circumstances.

When both the withFieldGroup ChildForm and the ParentForm have nested values with the same key, like street, the fields= {{ ... }} contract is fulfilled by coincidence. This leads to the unexpected double dots.

However, if you avoid using the same key street in both places, you can not fulfill the fields contract— which I assume is the intended behavior.

So, I think the solution is to avoid using nested values inside withFieldGroups. What do you think?

@LeCarbonator
Copy link
Contributor

@stefan-huck
Oh, I see ... Nono, you are spot on, this is unintended behaviour that needs fixing. Great catch!

Can you amend the unit test I provided so we don't regress on this? I'll merge once that's done.

@stefan-huck
Copy link
Contributor Author

stefan-huck commented Jul 29, 2025

@LeCarbonator I am not sure if my fix helps. Having two nested Forms with different keys street & streetAlternative:

const ChildForm = withFieldGroup({
  defaultValues: {} as {
    foo: string
    // Rename
    address: { streetAlternative: string }
  },
...

export function ParentForm() {
  const form = useAppForm({
    defaultValues: {
      street: "some-street-value",
      foo: "bar",
    },
  })
  return (
    <form.AppForm>
      <ChildForm
        form={form}
        fields={{
          foo: "foo",
          address: "address.street",
        }}
      />
    </form.AppForm>
  )
}

Results in address: never

 fields: {
  foo: "foo" | "street";
  address: never;
}

Imo nested values in withFieldGroups doesn't work as expected. I would expect fields to look like:

fields: {
  foo: "foo" | "street";
  address: {
    streetAlternative: "foo" | "street"
  };
}

@LeCarbonator
Copy link
Contributor

@stefan-huck yes, the field map is shallow. It doesn't recursively check nested values. The reasoning for that was that records and arrays cannot be mapped with this kind of structure, which already blocks them from being used at top level. On top of that, if you have a group of fields with the intent to map them to different keys, then you don't need namespaces such as the address object.

address.street in a field group may as well be an arbitrary name called addressStreet if you plan on remapping it to a different path. However, if you have address objects as compound field, then you expect the form to map to that object value (or something that satisfies it).

@LeCarbonator LeCarbonator changed the title fix(form-core): Handle nested withFieldGroup correct fix(form-core): prevent duplicate dots when concatenating withFieldGroup paths Jul 30, 2025
@LeCarbonator LeCarbonator merged commit 051e87b into TanStack:main Jul 30, 2025
6 checks passed
@LeCarbonator
Copy link
Contributor

Thanks a lot for the quick fix!

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.

withFieldGroup field path mapping for object mappings creates wrong paths
2 participants