react-hook-form register type conflict

I want to make a custom form input field component with react-hook-form. Here is my code.

// InputField.tsx

interface InputFieldProps {
  label: string
  register: UseFormRegister<FieldValues>
}

const InputField = ({ label, register }: InputFieldProps) => {
  return (
    <div>
      <label htmlFor="input">{label}</label>
      <br></br>
      <input
        {...(register(label), { required: true })}
        id="input"
        type="text"
        placeholder={`Enter your ${label}...`}
      />
    </div>
  )
} 

// MyForm.tsx

interface IFormValues {
  email: string
  password: string
}

const MyForm = () => {
  const { register, handleSubmit } = useForm<IFormValues>()

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <InputField label="password" register={register} /> // register type error
      <button type="submit">Submit</button>
    </form>
  )
}

But I’m getting this error

Type 'UseFormRegister<IFormValues>' is not assignable to type 'UseFormRegister<FieldValues>'.
Type 'FieldValues' is missing the following properties from type 'IFormValues': email, password

How do I type the register correctly? Or is there better approach to this?

>Solution :

The first error is that you are creating a register from a useForm with a explicit set type IFormValues type, and the expected, as you set in the InputField.tsx, is FieldValues

//Myform.tsx
const { register, handleSubmit } = useForm<IFormValues>()
//InputField.tsx
register: UseFormRegister<FieldValues> //should be the same type as MyForm.tsx

Normally, you don’t have to explicity set the type when using useForm. It will acquire the right one by getting the fields you are registering.

And the second one, you are telling your register that every field you are going to register has an email and password. So when you type:

<InputField label="password" register={register} />

Your register was kind of expecting something like:

<InputField label="password" label="email" register={register} />

My opinion: let typescript infer the correct type where it can, and use defaultValues and useFormContext. defaultValues will inter for you the type of register, so you could just copy that type into your InputField.tsx, if you want to keep passing register as a prop. useFormContext will create a register for you from the already opened useForm. I would use the following (I think its a cleaner, simpler approach):

// InputField.tsx
interface InputFieldProps {
  label: string
}

const InputField = ({ label }: InputFieldProps) => {
  const { register } = useFormContext();
  return (
    <div>
      <label htmlFor="input">{label}</label>
      <br></br>
      <input
        {...(register(label), { required: true })}
        id="input"
        type="text"
        placeholder={`Enter your ${label}...`}
      />
    </div>
  )
} 

// MyForm.tsx
const MyForm = () => {
  const { handleSubmit } = useForm({
    defaultValues: {email: '', password: ''}
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <InputField label="email"/> //i think this was missing
      <InputField label="password"/>
      <button type="submit">Submit</button>
    </form>
  )
}

OBS: if you open a form using useForm, and then another somewhere else in the same page, useFormContext will get lost and throw an error, because it does not know to which form you are referring and it does not allow you to specify it. Be careful with this.

Leave a Reply