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?
CodePudding user response:
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.