I am using React Hook Form to handle form validation in my React project. While everything works fine when using plain elements, I encounter issues when I try to wrap the inputs in a custom component. Specifically, validations such as minLength and maxLength are not being triggered properly. It is always countering the required validation.
Here’s an example of my setup:
Parent Component (Parent.jsx):
import { useForm } from "react-hook-form";
import Input from "./Components/Input.jsx";
import Button from "./Components/Button.jsx";
export default function Parent() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
const login = (data) => {
console.log("Form Data:", data);
};
return (
<div className="App">
<div className="container">
<form onSubmit={handleSubmit(login)}>
<Input
type="text"
name="username"
{...register("username", {
required: {
value: true,
message: "Username is required",
},
minLength: {
value: 5,
message: "Too Few Characters",
},
maxLength: {
value: 15,
message: "username length should not exceed 15",
},
})}
/>
{errors.username && <p className="red">{errors.username.message}</p>}
<Input
type="password"
name="password"
{...register("password", {
required: {
value: true,
message: "password is required",
},
minLength: {
value: 6,
message: "Password length should be greater than 6",
},
maxLength: {
value: 15,
message: "Password length should not exceed 15",
},
})}
/>
{errors.password && <p className="error-red">{errors.password.message}</p>}
<Button type="submit" />
</form>
</div>
</div>
);
}
custom Input Component (Input.jsx):
import { forwardRef } from "react";
const Input = forwardRef(function Input(
{ type = "text", name = "", ...props },
ref
) {
return (
<>
<input
placeholder=" "
className="txt-input"
type={type}
ref={ref}
{...props}
/>
{name && (
<label className="label-placeholder">
{name.charAt(0).toUpperCase() + name.slice(1)}
</label>
)}
</>
);
});
export default Input;
- Using forwardRef to forward the ref from React Hook Form to the native .
- Passing all props (e.g., onChange, onBlur) from the parent component to the custom component.
2
Answers
Looking at the Docs for Advanced Usage, it looks like you need to call register directly on the low-level components (e.g
<input />
)The example code they use (under the "Connect Form" heading):
The key part of this is making sure that you’re using
register
from the Context that is created (likely fromFormProvider
and made accessible fromuseFormContext
); it’s likely this is an abstracted version ofuseContext
andcreateContext
In your example it likely means the code would look like this:
Note: You may not need to pass
name={name}
in theCustomInput
, as I would expectregister
to create that prop alreadyThe
Input
component should pass/forward all field props to the underlyinginput
element, e.g. the missingname
prop soreact-hook-form
can validate the field data correctly.And for accessibility, you may also want to connect the rendered
label
to theinput
since it’s not wrapping it so when it’s clicked/interacted with the input is focused.Example: