T
TanStack2w ago
tame-yellow

custom reusable form.Field component, trying to use Form Composition

Hello, I am reading the docs of Form Composition and I just saw the example of creating a form.Subscribe wrapper for a subscription button, so I guess that, for example, I could create a reusable component to be used for all my form.Field text inputs. This is my first attempt:
import type { ZodObject, ZodRawShape } from "zod";
import LabeledField from "@/components/form/labeled-field";
import { isFieldRequired, useFormContext } from "@/components/form/utils";
import { Input } from "@/components/ui/input";

interface FieldInputProps {
id: string;
fieldName: string;
label: string;
schema: ZodObject<ZodRawShape>;
}

export default function FieldInput({ id, fieldName, label, schema }: FieldInputProps) {
const form = useFormContext();

return (
<form.Field
name={fieldName}
children={({ state, handleChange, handleBlur }) => (
<LabeledField labelFor={id} label={label} required={isFieldRequired(fieldName, schema)}>
<Input id={id} value={state.value ?? ""} onChange={(e) => handleChange(e.target.value)} onBlur={handleBlur} />
</LabeledField>
)}
/>
);
}
import type { ZodObject, ZodRawShape } from "zod";
import LabeledField from "@/components/form/labeled-field";
import { isFieldRequired, useFormContext } from "@/components/form/utils";
import { Input } from "@/components/ui/input";

interface FieldInputProps {
id: string;
fieldName: string;
label: string;
schema: ZodObject<ZodRawShape>;
}

export default function FieldInput({ id, fieldName, label, schema }: FieldInputProps) {
const form = useFormContext();

return (
<form.Field
name={fieldName}
children={({ state, handleChange, handleBlur }) => (
<LabeledField labelFor={id} label={label} required={isFieldRequired(fieldName, schema)}>
<Input id={id} value={state.value ?? ""} onChange={(e) => handleChange(e.target.value)} onBlur={handleBlur} />
</LabeledField>
)}
/>
);
}
However... The name prop shows Type 'string' is not assignable to type 'never'.ts(2322), and state is never too. Am I doing something wrong?
18 Replies
official-silver
official-silver2w ago
this is conflicting code. You have field context, which means an AppField has been used, yet you create another field inside of the same name the field components are meant for the UI side of things (the components in the field callback) if you need to encapsulate logic as well, then field groups are the way to go logic as in validators and dependent fields
tame-yellow
tame-yellowOP2w ago
Whoops 😅🙈 updated snippet
official-silver
official-silver2w ago
form context could come from any form, so value-related things are not accessible through it. That includes fields
tame-yellow
tame-yellowOP2w ago
Hmmmmmm but I guess I want to use a formComponent, right?
official-silver
official-silver2w ago
what for? the snippet‘s basically a golden example of a field component
tame-yellow
tame-yellowOP2w ago
Oh... that means I can not use the <form.Field> in a generic way? I could pass the value related data as props I guess Ohhhh wait... you mean using my "FieldInput" component as a field component will allow me to replace the <form.Field> components with <form.FieldInput> components?
official-silver
official-silver2w ago
not quite it‘ll be a component accessed from the field within an AppField child
(

<form.AppField
name="firstName"
children={(field) => <field.TextField label="First Name" />}
/>
)
(

<form.AppField
name="firstName"
children={(field) => <field.TextField label="First Name" />}
/>
)
tame-yellow
tame-yellowOP2w ago
Oh okay, I think I got it, I am reading the docs again, but I still have the same question, could I use <form.Field> in a generic way in a field/form component? 😅
official-silver
official-silver2w ago
at runtime, technically yes. Type-safe, absolutely not it also merges the logic concern and ui concern into one component which is why the AppField is still externally written
tame-yellow
tame-yellowOP2w ago
Oh okay, tomorrow I will refactor my repeated code using field components, thankss! ❤️ In exchange for you help, I fixed a typo: https://github.com/TanStack/form/pull/1688
GitHub
Fix typo by Jaime02 · Pull Request #1688 · TanStack/form
"createFromHook" -> createFormHook
official-silver
official-silver2w ago
… I wonder how many people missed that by the way, are you migrating from rhf? if so, I can give better pointers in the future since the syntax is quite different
tame-yellow
tame-yellowOP2w ago
What is that? 🥸 I am building from scratch
official-silver
official-silver2w ago
another form library that‘s common in React, React Hook Form
tame-yellow
tame-yellowOP2w ago
Ohh no no, I didn't know it Hey @Luca | LeCarbonator , after switching my components to custom form fields, now the issue of opening the dialog -> edit a field -> close the dialog -> open it again -> the value is still modified 🙁 is happening again 😭 Using a key for the form is not enough I guess because my source data is the in both cases. Shall I use form.reset? I would like to avoid it...
official-silver
official-silver2w ago
so what‘s the component tree right now?
<Container> // contains defaultValues
<Dialog>
<Form />
</Dialog>
</Container>
<Container> // contains defaultValues
<Dialog>
<Form />
</Dialog>
</Container>
Is it this? if dialog closing is programmatic (and you'd like to undo changes that were made), then yes, calling form.reset() on dialog closing is the way to go no need to give it actual values to reset to, since that's still handled by the code above
tame-yellow
tame-yellowOP2w ago
Hmm yes, the dialog executes the query and provides the form values in the Edit or Readonly cases, and a hardcoded default values object is used for the create case I think I was previously solving this issue using conditional rendering of the shadcn dialog, which is not a good practice, the dialog was instantly dissapearing and I was getting apollo query errors 😵‍💫 I am going to try to use form.reset instead of the defaultValues argument, I guess this way the form types will be inferred from the form.reset call instead, right?
official-silver
official-silver2w ago
conditional rendering of a shadcn dialogue is bad practice? according to shadcn? no, defaultValues is the inferring source. the reason you can provide overrides for form.reset() is because sometimes you want to reset the form while not preserving the old defaultValues if no argument is provided, it simply falls back to defaultValues, which you can update if needed
tame-yellow
tame-yellowOP2w ago
I have not seen anyone asking this question online 🥸 ChatGPT / Gemini / Claude say it is possible to do, but has pros and cons, etc... I will avoid it because of the closing animation and the query errors Hmmm I mean that I can move all my defaultValues: <tons of fields> to form.reset(<tons of fields>), right? I want to avoid duplicating the code of <tons of fields> I guess I can, it now works like a charm 🥰 Thanks for you help!! I really appreciate it

Did you find this page helpful?