javascriptreactjsreact-hook-form

How to handle React Hook Form validation within a custom input component?


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;

  1. Using forwardRef to forward the ref from React Hook Form to the native .
  2. Passing all props (e.g., onChange, onBlur) from the parent component to the custom component.

Solution

  • The Input component should pass/forward all field props to the underlying input element, e.g. the missing name prop so react-hook-form can validate the field data correctly.

    And for accessibility, you may also want to connect the rendered label to the input since it's not wrapping it so when it's clicked/interacted with the input is focused.

    Example:

    const Input = forwardRef(function Input(
      { name = "", type = "text", ...props },
      ref
    ) {
      return (
        <>
          <input
            ref={ref}
            className="txt-input"
            id={name}   // <-- for accessibility with label
            name={name} // <-- for field validation
            placeholder=" "
            type={type}
            {...props}
          />
          {name && (
            <label
              className="label-placeholder"
              htmlFor={name} // <-- for accessibility with input
            >
              {name.charAt(0).toUpperCase() + name.slice(1)}
            </label>
          )}
        </>
      );
    });