form icon indicating copy to clipboard operation
form copied to clipboard

withForm types break if defaultValues includes any extra field

Open ljukas opened this issue 9 months ago • 13 comments

Describe the bug

Using withForm breaks whenever any extra field is added to the parents defaultValues.

import './App.css'
import { useAppForm, withForm } from './hooks/form'

function App() {

  const form = useAppForm({
    defaultValues: {
      form1: '',
      form2: ''
    }
  })

  return (
    <div>
      <ChildForm1 form={form} />
    </div>
  )
}

export default App


const ChildForm1 = withForm({
  defaultValues: {
    form1: ''
  },
  render: ({form}) => {
    return (
      <div />
    )
  }
})

Your minimal, reproducible example

https://github.com/ljukas/tanstack-withform-multiple

Steps to reproduce

Create a child form using withForm from the createHookForm api. Use useAppForm, add the required defaultValues for the child form, then add any extra field to the parents defaultValues. The child-form form props types will no give a red squiggly line.

This also means you cannot compose multiple child forms together, or you can but you get type errors on the childform form-prop

Expected behavior

As I user I expect the child-form to only care about the fields that it itself needs. So that I can compose multiple child forms together, which I expect is the use case from the start.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

All

TanStack Form adapter

react-form

TanStack Form version

1.1.0

TypeScript version

5.7.2

Additional context

No response

ljukas avatar Mar 17 '25 12:03 ljukas

As far as I know, you must pass the same default values to withForm HOC as you pass in main useForm hook. if you are passing form options, then you can share this with different withForm HOC to remain consistent to avoid issues.

MusKRI avatar Mar 17 '25 18:03 MusKRI

This relates to #1273 where the types can be misleading for subforms/parent forms/formOptions.

@MusKRI is correct. The types must be exactly the same, to do this you spread the values you defined from formOptions. withForm is not intended to define custom defaultValues/validators at the subForm level.

See also #1292 is also facing a similar issue.

IMO we should close this issue and add the details to #1273 since that's the main cause of this.

juanvilladev avatar Mar 17 '25 21:03 juanvilladev

@MusKRI Which is what Im trying to report as an error. If the child form needs to have exactly the same default values as the main form it is no longer a child form, it is the form. Say you want to reuse the same child form in two different forms that have seperate values except for the values that the child form needs. You cannot do that reliably atm.

#1273 seems to be a different issue. Using two different child forms inside a parent form and spreading their formOptions will override each others values, so doing that is not reliable.

const childForm1Opts = formOptions({
 defaultValues: {
   form1: ''
}})

const childForm1Opts = formOptions({
 defaultValues: {
   form3: ''
}})

useAppForm({
...childForm1Opts,
...childForm2Opts,
})

Doing this will not merge the options, and you would then for every sub-part of the formOptions spread them instead, which would lead to things like this

useAppForm({
 ...childForm1Opts,
...childForm2Opts,
defaultValues: {
  ...childForm1Opts.defaultValues,
  ...childForm2Opts.defaultValues,
}})

etc, so if this is the target api there would need to some form of merge function so this can done better

ljukas avatar Mar 18 '25 15:03 ljukas

Say you want to reuse the same child form in two different forms that have seperate values except for the values that the child form needs. You cannot do that reliably atm.

Yeah I thought that was the whole point, bit confusing!

Velua avatar Mar 23 '25 07:03 Velua

Which is what Im trying to report as an error. If the child form needs to have exactly the same default values as the main form it is no longer a child form, it is the form. Say you want to reuse the same child form in two different forms that have seperate values except for the values that the child form needs. You cannot do that reliably atm.

This.

mackehansson avatar Apr 11 '25 08:04 mackehansson

Running into the same problem trying to use child form (withForm) as shared field between four different forms that would include those. Any suggestion to avoid massive duplication? It's also an issue when trying to use same child form for creating and editing some entity.

bokunobaka avatar Apr 13 '25 18:04 bokunobaka

This was also my desired behavior. I only want type checking to ensure that the form I am passing to my child form is a superset of the child forms structure.

As an aside, it looks like the issues with this type checking stem from how the validators are checked. Form validators in general seem problematic to me. Validator input types are checked against the default values type rather than the default values being checked against the validator input type. I'm not sure why this decision was made but it leads to cases where your defaultValues type is more strict than your validator which causes errors. e.g.

const form = useForm({
    defaultValues: {
        value: '' // string
    },
    validators: {
        onSubmit: z.object({
            value: z.string().optional(), // input type is string or undefined which causes a type error
        }
    }
}

rob-steele-active avatar Apr 16 '25 19:04 rob-steele-active

Which is what Im trying to report as an error. If the child form needs to have exactly the same default values as the main form it is no longer a child form, it is the form. Say you want to reuse the same child form in two different forms that have seperate values except for the values that the child form needs. You cannot do that reliably atm.

This.

Also one upping this, this is exactly what I thought I could do with the withForm HOC but have been running into all the same issues as above.

I don’t see the real power of withForm HOC without the ability to use it to create sub forms with a subset of form options that should all align in the parent useAppForm hook.

ForrestDevs avatar Apr 17 '25 02:04 ForrestDevs

Also another similar issue is here, it was closed but I don’t think it was ever truly resolved #1224

ForrestDevs avatar Apr 17 '25 02:04 ForrestDevs

Does anyone happen to have a suggestion for a workaround to not copy-paste shared parts of forms?

 z.object({
	base: z.object({
		id: z.number(),
		name: z.string().min(1),
		dueDate: z.string().date(),
		...
	}),
 	type_specific: z.object({
		value_1: z.coerce.number(),
		value_2: z.coerce.number(),
		...
	})
});

Or are there any plans to add something that would support this?

bokunobaka avatar May 10 '25 13:05 bokunobaka

This is the same problem I'm running into. I setup withForm for a set of fields that is relevant to multiple resources that I have. However, I can only use them with one resource because the defaultValues for withForm have to contain the values for the parentForm.

This means any childForm created using withForm is tied to exactly one parent form.

I'm confused if this is the desired behavior of withForm. If it isn't the desired behavior is there another approach to doing what we're describing?

jackeliottmuller avatar May 20 '25 20:05 jackeliottmuller

I am facing the same issue here too. I have a child form that I would like to use in a seperate form unrelated to the first one, so I thought I could maybe split it off into its own using its onw formOptions() method.

export const firstFormOptions = formOptions({
  defaultValues: {
    productId: 0,
    priceProducts: [],
  } as {
    productId: number;
    priceProducts: ProductDetail[];
  },
});

export const secondFormOptions = formOptions({
  defaultValues: {
    price: 0,
    priceProducts: [],
  } as {
    price: number;
    priceProducts: ProductDetail[];
  },
});

export const childFormOptions = formOptions({
  defaultValues: {
    priceProducts: [],
  } as {
    priceProducts: ProductDetail[];
  },
});

const ChildForm = withForm({
  ...childFormOptions,
  props: {
    something: [] as Something[],
  },
  render: ({ form, something }) => {
    return (
      <div>

      </div>
    );
  },
});

In my mind the form using firstFormOptions should be able to use any withForm components implementing childFormOptions without type issues. And then I could use the childForm in an unrelated secondForm

I mean the JavaScript works, I can actually do this. But I would have to silence the type errors.

tollanes avatar Jun 05 '25 13:06 tollanes

That is the intended design of withForm. The reusable sections of a form will be implemented as field groups in #1469

LeCarbonator avatar Jun 05 '25 16:06 LeCarbonator