reactjstypescriptreact-hook-form

Handle multiple types 'react-hook-form'


Currently I'm working in a component that has two children, each children has their specific components and their own form, the structure of the data is similar. I'm using react js with hook form to handle forms, the problem is that I want to use the same form for both children component, the parent component is the one that handles the form but the children needs to be able to add more data into one array that belongs to the parent type.

Here is my code:

function ParentComponent() {
  const form = useForm<FormType>()
  const { fields } = useFieldArray({ control: form.control ,name: `shirts`,});

  return (
    <div>

     Parent
     {fields.map((item)) => {
      return(
       <>
        <Child1 form={form} />
        <Child2 form={form} />
       </>
      )
    }}
     
    </div>
  )
}

const Child1: React.FC<Child1Props> = ({form, index}) => {
const { fields: shirts } = useFieldArray({
    control: form.control,
    name: `shirts.${index}.sleeves`,
  });

return (
  <div>
   {shirts.map((shirt) = {
    return (
      <span>{shirt.name}<span>
      <button>remove sleeve</button>
    )
    
  })}
  
  </div>
 )
}

const Child2: React.FC<Child2Props> = ({form, index}) => {
const { fields: shirts } = useFieldArray({
    control: form.control,
    name: `shirts.${index}.necklace`,
  });

return (
  <div>
    {shirts.map((shirt) = {
    return (
      <span>{shirt.name}<span>
      <button>remove necklace</button>
     )
    })}
  </div>
)
}

As you can see I have to handle sleeves and necklace in the child components, now when I try to pass the form from the parent as prop to the children, I'm getting an error for the type mismatch, since sleeve and necklace are different types but they have the same structure but different names on the props. And the paren component as well is an array of fields


Solution

  • Instead of passing the form object to the children, you should wrap your children in the <FormProvider> and use the useFormContext hook in the children.

    First create the types for your form like so:

    export interface Shirt {
      sleeves: { name: string }[];
      necklace: { name: string }[];
    }
    
    export interface FormType {
      shirts: Shirt[];
    }
    

    In the parent component, wrap your children in the <FormProvider> this will make the children have access to the useFormContext hook that you can use to edit the form.

    function ParentComponent() {
      const methods = useForm<FormType>();
      const { fields, append } = useFieldArray({
        control: methods.control,
        name: "shirts",
      });
    
      return (
        <FormProvider {...methods}>
          <form>
            {fields.map((item, index) => (
              <div key={item.id}>
                {/* No more form prop */}
                <Child1 index={index} />
                <Child2 index={index} />
              </div>
            ))}
    
            {/*this is the default value for your shirt, which contains empty sleeves and neckless so that you can add it from your children without type errors */}
    
            <button type="button" onClick={() => append({ sleeves: [], necklace: [] })}>
              Add Shirt
            </button>
          </form>
        </FormProvider>
      );
    }
    

    in the children component, you use the useFormContext to add your sleeves or neckless.

    import { useFormContext, useFieldArray } from "react-hook-form";
    
    const Child1 = ({ index }: { index: number }) => {
    
      const { control } = useFormContext<FormType>(); 
      const { fields, append, remove } = useFieldArray({
        control,
        name: `shirts.${index}.sleeves`,
      });
    
      return (
        <div>
          {/* UI to manage sleevs*/}
        </div>
      );
    };
    

    Create a similar component for your Child2 for neckless.