#Reusable Forms

15 messages · Page 1 of 1 (latest)

crude kelp
#

Disclaimer: I think this came up already but I can't find it

I have an Address-Form where I collect the Street, Postalcode and City of the user.

I want to re-use the form for multiple purposes (Sign-Up, User-Profile, and Invoicing)

function AddressFormPart(
  // <-- what should I pass here?
) {
  return (
    <>
      <form.Field
        name="address.street"
        children={(field) => (
          <input
            id={field.name}
            name={field.name}
            value={field.state.value}
            onBlur={field.handleBlur}
            onChange={(e) => field.handleChange(e.target.value)}
          />
        )}
      />

      <form.Field
        name="address.city"
        children={(field) => (
          <input
            id={field.name}
            name={field.name}
            value={field.state.value}
            onBlur={field.handleBlur}
            onChange={(e) => field.handleChange(e.target.value)}
          />
        )}
      />

      <form.Field
        name="address.postalCode"
        children={(field) => (
          <input
            id={field.name}
            name={field.name}
            value={field.state.value}
            onBlur={field.handleBlur}
            onChange={(e) => field.handleChange(e.target.value)}
          />
        )}
      />
    </>
  )
}

function SignUp() {
  const form = useForm({
    defaultValues: {
      username: "",
      // more data to be collected…
      address: {
        street: "",
        city: "",
        postalCode: "",
      } 
    }
  })

  function handleSubmit() {}

  return (
    <form onSubmit={handleSubmit}>
      <form.Field
        name="username"
        children={(field) => (
          <input
            id={field.name}
            name={field.name}
            value={field.state.value}
            onBlur={field.handleBlur}
            onChange={(e) => field.handleChange(e.target.value)}
          />
        )}
      />
    </form>
  )
}
#

I'm also using validatorAdapter: zodValidator() - but wanted to keep it simple(r) in the example code

late glen
crude kelp
iron prawn
#

im making new api

crude kelp
#

I think what I'm after is the Reusable Form PARTS - so a couple of fields that can be re-used in a parent-form

You're example looks like a "reusable form" (without the "part").

crude kelp
#

Slapping some any on it "fixes" the type issues but then I'm not typesafe in the FormPart-Component anymore…

function FormPart({ form }: { form: ReactFormExtendedApi<any, ZodValidator> }) {
  return (
    <form.Field name="THIS-STRING-IS-NOT-TYPESAFE-ANYMORE" />
  )
}
left fiber
#

This is how I managed to make it typesafe.

type ExtractFormData<T> = T extends ReactFormExtendedApi<infer U, any> ? U : never;

type InputProps<TFormData extends ReactFormExtendedApi<any, any>> = {
  form: TFormData;
  name: keyof ExtractFormData<TFormData>;
  label: string;
};

const Input = <TFormData extends ReactFormExtendedApi<any, any>>({
  form,
  name,
  label,
}: InputProps<TFormData>) => (
  <form.Field name={name as string}>
    {(field) => (
      <>
        <label htmlFor={field.name}>{label}</label>
        <input
          id={field.name}
          name={field.name}
          value={field.state.value}
          onBlur={field.handleBlur}
          onChange={(e) => field.handleChange(e.target.value)}
        />
        <FieldInfo field={field} />
      </>
    )}
  </form.Field>
);

This results in:

<Input form={form} name="firstName" label="First Name" />
cedar rock
#

I wouldn't pass the whole form instance

left fiber
#

Is there a way to make name typesafe without passing the entire form instance?

cedar rock
#

Can't you pass just form.field ?

crude kelp
#

<form.Field name={name as string}> casting to string seems suspicious… You're basically telling Typescript here "It's fine, it's a string - trust me", right? So if you pass "foobar" it wouldn't show up as an error in your IDE?

left fiber
#

The typesafety is on the root name variable not the one on Field.

So when you are writing <Input form={form} name="" /> name will be typesafe based on what you passed into useForm