Integrate React Hook Forms with Existing Form Components

React Hook Form is a form handler that handles validation, error, etc using hooks. The library also has great integration with Typescript. You can read more about the it here: React Hook Form

I found the best part of the library is the ease of integrating it into existing legacy code with minimal adjustments.

The Get Started documentation will walk you through how to use the useForm hook and register option for form elements. This is a great option if you are not using third-party form elements like React Select, Antd, Material UI, or legacy custom form element components.

For our sample case, I have the following UserProfile.jsx component, with a custom input component.

UserProfile.jsx

export const UserProfile = () =>{

 const [userName, setUserName] = useState('')
 const [email, setEmail] = useState('')

const onSubmit = async()=> {
  await axios.post('/user',{userName, email})
    .then(()=>console.log('Success'))
    .catch((e)=> console.error(e))
}

 return (
  <div>
    <CustomInput 
      type='text'
      label='User Name'
      name='userName' 
      value={userName} 
      onChange={setUserName}
    />
    <CustomInput 
      type='text'
      label='Email'
      name='email' 
      value={email} 
      onChange={setEmail}
    />
    <button onClick={onSubmit}>
     Submit
    </button>
  </div>
 )
}

The CustomInput.jsx component:

export const CustomInput = (
  {
    name,
    type = 'text',
    label,
    disabled = false,
    value,
    onChange,
  },
) => {
return (
    <>
        <label htmlFor={name}>{label}</labe>
        <input
          value={value}
          onChange={(v) => onChange(v.target.value)}
          type={type}
          disabled={disabled}
        />
    </>
  )
}

In the cases of the third-party libraries, React Hook Form recommends we use the Controller component to wrap the third-party component. You can read more about it here: Controller

We can use the same Controller functionality through the useController hook to update CustomInput.jsx component.

Updated CustomInput.jsx

export const CustomInput = (
  {
    name,
    type = 'text',
    label,
    disabled = false,
    controller,
    rules /**A way to set input validation**/
  },
) => {

const { field } = useController({
      name, /**This is the unique identifier used by React Hook Form**/
      rules,
      control,
    })

return (
    <>
      <label htmlFor={name}>{label}</label>
      <div>
        <input
           {...field} /**this allows React Hook Form to handle the value, onChange and other form functionalities**/
          type={type}
          disabled={disabled}
        />
      </div>
    </>
  )
}

Every time the CustomInput component is used it will require the controller prop. Now we modify the parent UserProfile.jsx component to use useForm hook and pass in the controller.

Use useForm Hook

First we remove the useState hook and use the useForm hook.

const {controller, handleSubmit} = useForm({
  defaultValues:{
    userName:'',
    email: '',
  }
 })

Update the props

Then update the props passed into the CustomInput Component.

<CustomInput 
      type='text'
      label='User Name'
      name='userName' 
      controller={controller}
      rules={{ required: true}} /** passing in the validation rule**/
    />

Update the button

Next we need to update the button to trigger the handleSubmit from the useForm hooks. This will allow us to use the validated form data in our onSubmit function.

<button 
  onClick={handleSubmit(
    (data)=>onSubmit(data)
   )}
>
  Submit
</button>

Update onSubmit

Last we need to update the onSubmit function to use the correct data.

const onSubmit = async(data) => {
  await axios.post('/user',{
      userName:data.userName, 
      email:data.email
    })
    .then(()=>console.log('Success'))
    .catch((e)=> console.error(e))
}

Our final UserProfile.jsx component looks as follows:

export const UserProfile = () =>{

/**Removed state and replaced it with useForm**/
 const {controller, handleSubmit} = useForm({
  defaultValues:{
    userName:'',
    email: '',
  }
 })

/**Updated the submit function to get validated data from the useForm hook**/
const onSubmit = async(data) => {
  await axios.post('/user',{
      userName:data.userName, 
      email:data.email
    })
    .then(()=>console.log('Success'))
    .catch((e)=> console.error(e))
}

 return (
  <div>
    <CustomInput 
      type='text'
      label='User Name'
      name='userName' 
      controller={controller}
      rules={{ required: true}} /** passing in the validation rule**/
    />
    <CustomInput 
      type='text'
      label='Email'
      name='email' 
      controller={controller}
      rules={{ required: true}}
    />
    <button 
      onClick={handleSubmit((data)=>onSubmit(data))} /**the data passes in the validated input values to the submit function**/
    >
     Submit
    </button>
  </div>
 )
}

With these changes we can continue to use existing form elements, and integrate the powerful tools of React Hook Forms with minimal changes.

24