I have a Form.js ponent which returns a form element. This form element contains a FormGroup ponent which takes props such as inputType, inputName, inputPlaceholder and in return renders an input field with a label. I am using react-hook-form for the validation of the input but I can't get it to work after extracting the input to a separate ponent. In my original code, I had my errors appear when the validation failed, but after extracting the label and input into their own ponent, this stopped working.
My original working code looks like this:
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import FormGroup from './FormGroup';
const Form = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
console.log(data)
};
return (
<form className="form-container" onSubmit={handleSubmit(onSubmit)} autoComplete="off" noValidate>
<div className="form-group">
<label className="form-label" htmlFor="firstName">
<p>First Name</p>
<p className="input-error">{errors.firstName && errors.firstName.message}</p>
</label>
<input type="text" name="firstName" placeholder="First Name" {...register("firstName", { required: 'Required ' })} />
</div>
<div className="form-group">
<button type="submit">Submit</button>
</div>
</form>
);
}
export default Form
Then I changed it to:
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import FormGroup from './FormGroup';
const Form = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
console.log(data)
};
return (
<form className="form-container" onSubmit={handleSubmit(onSubmit)} autoComplete="off" noValidate>
<FormGroup
inputType="text"
inputName="firstName"
inputPlaceholder="First Name">
</FormGroup>
<div className="form-group">
<button type="submit">Submit</button>
</div>
</form>
);
}
export default Form
And I extracted the label and input into:
import { useForm } from 'react-hook-form';
const FormGroup = (props) => {
const { register, formState: { errors } } = useForm();
return (
<div className="form-group">
<label className="form-label" htmlFor={props.inputName}>
<p>{ props.inputPlaceholder }</p>
<p className="input-error">{errors.firstName && errors.firstName.message}</p>
</label>
<input type={props.inputType} name={props.inputName} placeholder={props.inputPlaceholder} {...register(props.inputName, { required: true })} />
</div>
);
}
export default FormGroup
I have a Form.js ponent which returns a form element. This form element contains a FormGroup ponent which takes props such as inputType, inputName, inputPlaceholder and in return renders an input field with a label. I am using react-hook-form for the validation of the input but I can't get it to work after extracting the input to a separate ponent. In my original code, I had my errors appear when the validation failed, but after extracting the label and input into their own ponent, this stopped working.
My original working code looks like this:
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import FormGroup from './FormGroup';
const Form = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
console.log(data)
};
return (
<form className="form-container" onSubmit={handleSubmit(onSubmit)} autoComplete="off" noValidate>
<div className="form-group">
<label className="form-label" htmlFor="firstName">
<p>First Name</p>
<p className="input-error">{errors.firstName && errors.firstName.message}</p>
</label>
<input type="text" name="firstName" placeholder="First Name" {...register("firstName", { required: 'Required ' })} />
</div>
<div className="form-group">
<button type="submit">Submit</button>
</div>
</form>
);
}
export default Form
Then I changed it to:
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import FormGroup from './FormGroup';
const Form = () => {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
console.log(data)
};
return (
<form className="form-container" onSubmit={handleSubmit(onSubmit)} autoComplete="off" noValidate>
<FormGroup
inputType="text"
inputName="firstName"
inputPlaceholder="First Name">
</FormGroup>
<div className="form-group">
<button type="submit">Submit</button>
</div>
</form>
);
}
export default Form
And I extracted the label and input into:
import { useForm } from 'react-hook-form';
const FormGroup = (props) => {
const { register, formState: { errors } } = useForm();
return (
<div className="form-group">
<label className="form-label" htmlFor={props.inputName}>
<p>{ props.inputPlaceholder }</p>
<p className="input-error">{errors.firstName && errors.firstName.message}</p>
</label>
<input type={props.inputType} name={props.inputName} placeholder={props.inputPlaceholder} {...register(props.inputName, { required: true })} />
</div>
);
}
export default FormGroup
Share
Improve this question
asked Apr 18, 2021 at 3:28
OnyxOnyx
5,78210 gold badges52 silver badges119 bronze badges
2 Answers
Reset to default 3The Problem
You have multiple instances of the useForm
hook.
Solution
Use the useForm
hook only on the Form
ponent and pass the errors
object and register
method as props to FormGroup
Form Component
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import FormGroup from './FormGroup';
const Form = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = async (data) => {
console.log(data);
};
return (
<form
className="form-container"
onSubmit={handleSubmit(onSubmit)}
autoComplete="off"
noValidate
>
<FormGroup
inputType="text"
inputName="firstName"
inputPlaceholder="First Name"
register={register}
errors={errors}
></FormGroup>
<div className="form-group">
<button type="submit">Submit</button>
</div>
</form>
);
};
export default Form;
FormGroup Component
import { useForm } from 'react-hook-form';
const FormGroup = (props) => {
return (
<div className="form-group">
<label className="form-label" htmlFor={props.inputName}>
<p>{props.inputPlaceholder}</p>
<p className="input-error">
{props.errors.firstName && props.errors.firstName.message}
</p>
</label>
<input
type={props.inputType}
name={props.inputName}
placeholder={props.inputPlaceholder}
{...props.register(props.inputName, { required: true })}
/>
</div>
);
};
export default FormGroup;
The remended approach from the Documentation is as follows:
useFormContext This custom hook allows you to access the form context. useFormContext is intended to be used in deeply nested structures, where it would bee inconvenient to pass the context as a prop. https://react-hook-form./api/useformcontext
const Form = () => {
const methods = useForm();
const {
register,
handleSubmit,
formState: { errors },
} = methods;
const onSubmit = async (data) => {
console.log(data);
};
return (
<FormProvider {...methods} >
<form
className="form-container"
onSubmit={handleSubmit(onSubmit)}
autoComplete="off"
noValidate
>
<FormGroup
inputType="text"
inputName="firstName"
inputPlaceholder="First Name">
</FormGroup>
<div className="form-group">
<button type="submit">Submit</button>
</div>
</form>
</FormProvider>
);
};
const FormGroup = (props) => {
const { register, errors } = useFormContext(); // retrieve all hook methods from parent
return (
<div className="form-group">
<label className="form-label" htmlFor={props.inputName}>
<p>{props.inputPlaceholder}</p>
<p className="input-error">
{errors.firstName && errors.firstName.message}
</p>
</label>
<input
type={props.inputType}
name={props.inputName}
placeholder={props.inputPlaceholder}
{register(props.inputName, { required: true })}
/>
</div>
);
};
export default FormGroup;