Published on

How to Use Custom UI Components with React Hook Form

Authors

Pretty much all our apps will have form in one way or another. Building it manually from scratch is not a stright forward in most of the UI framework like React/Vue/Angular. We have to handle lot of things like performance, validation, error handling, manage it's state and handling data.

Thanks to library like React Hook Form makes it relatively easy for us to build form.

Let's see how to use custom components with it.

Dependencies

1. UI - Tailwind CSS

For the sake of simplicity, I'll be using Tailwind CSS and that too CDN approach for the demo app that we're building.

<script src="https://cdn.tailwindcss.com"></script>

You can read more about how to setup the proper in their docs

2. Install the necessary packages

In our case, I'll be using Headless UI as custom components with the form.

So we need to install that as dependency as well.

npm i react-hook-form@7.37.0 @headlessui/react@1.7.3 

Approach #1: By using "control" prop with useController()

We need to get control object from useForm() when creating a form and pass it to the custom component.

This object contains methods for registering components into React Hook Form.

// App.js
import { Switch } from "@headlessui/react";
import { useForm } from "react-hook-form";
import CustomSwitch from "./CustomSwitch";

export default function App() {
  const { handleSubmit, control } = useForm();
  const onSubmit = async (formData) => {
    console.log(formData);
  };
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
        <Switch.Group as="div">
            {/* 👇 We're setting name and passing control object from useForm() */}
            <CustomSwitch name="default_header" control={control} />
        </Switch.Group>
         
         <button type="submit">
            Submit
          </button>
    </form>
  )

}
// CustomSwitch.js
import { useController } from "react-hook-form";

import { Switch } from "@headlessui/react";
import { useForm } from "react-hook-form";
import CustomSwitch from "./CustomSwitch";

export const CustomSwitch = (props) => {
  const {
    field: { value, onChange }
  } = useController(props);

  return (
    <>
      <Switch
        checked={Boolean(value)}
        onChange={onChange}
        />
    </>
  );
};

export default CustomSwitch;

And in the custom component we can pass the props to useController() hooks to get the value of the field and also other callback functions like onChange

Approach #2: Render props approach

Similar to that of the above approach we can also use render props

// App.js
import { Switch } from "@headlessui/react";
import { useForm, Controller } from "react-hook-form";
import CustomSwitch from "./CustomSwitch";

export default function App() {
  const { handleSubmit, control } = useForm();
  const onSubmit = async (formData) => {
    console.log(formData);
  };
  return (
    <form onSubmit={handleSubmit(onSubmit)}>
        <Controller
            render={({ field }) => <input type="text" {...props} />}
            name="name"
            control={control}
        />
         
         <button type="submit">
            Submit
          </button>
    </form>
  )
}

Demo

Here is a demo of all the approaches together :)

References