DEV Community

Cover image for React Hook Form with Controllers
Apoorva CG
Apoorva CG

Posted on • Updated on

React Hook Form with Controllers

Hi all!πŸ‘‹

Today here in this post we'll see how we can use Controlled components from react-hook-form( let's call it 'rhf' ) library for building forms.

rhf lets us manage form state, handle validations and errors, use controlled components and many more functionalities to our forms react form without useState() hook for handling form values. Let's go ahead and install rhf in a CRA-setup project and see how it works,

yarn add react-hook-form
Enter fullscreen mode Exit fullscreen mode

or

npm install react-hook-form
Enter fullscreen mode Exit fullscreen mode

We will be integrating material-ui, react-select libraries with rhf form, so let's install these dependencies and get started with itπŸ’«

yarn add react-select @mui/material @emotion/react @emotion/styled @material-ui/core
Enter fullscreen mode Exit fullscreen mode

We will be handling errors, dependent input fields, individually reset fields, reset the entire form and finally submit the form.

Now that we have installed all the dependencies, let's see how can we implement form with rhf😎. To begin with, we will import useForm and Controller from rhf.

useForm() is a custom hook that,
β†ͺ️ takes defaultValues, mode, etc., as arguments and
↩️ returns formState, handleSubmit, watch, setFocus, clearErrors etc., as objects and functions to manage forms.

import { useForm, Controller } from "react-hook-form";

const defaultValues = {
  name: "",
  email: "abc@xyz.com",
  address: "",
  country: { value: "", label: "" },
  state: { value: "", label: "" }
};

export default function App() {
  const { handleSubmit, reset, control, watch, resetField } = useForm({
    defaultValues
  });
  return(<form></form>);
}
Enter fullscreen mode Exit fullscreen mode

Controller is a wrapper component takes number of props, we will be using

πŸ‘‰ name, unique & required
πŸ‘‰ render, takes any React components.
πŸ‘‰ rules, helps to validate fields
πŸ‘‰ control, registers component to rhf

  <Controller
    name="name"
    control={control}
    rules={{ required: true }}
    render={({ field: { onChange, value }, fieldState: { error 
              } }) => {
              return (
               <>
                 <TextField
                  placeholderText="Your name goes here!"
                  type="text"
                  name="name"
                  value={value}
                  label="Name"
                  variant="outlined"
                  onChange={onChange}
                  error={error}
                  helperText={error ? "Name is required" : ""}
                 />
               </>
              );
         }}
 />
Enter fullscreen mode Exit fullscreen mode

The render prop attaches events and value into the component. Provides onChange, onBlur, name, ref and value to the child component, and also a fieldState object which contains specific input state to handle input fields element.

For the same form we will continue by adding react-select,

      <Controller
        name="country"
        control={control}
        rules={{
          required: true,
          validate: {
            isCountry: (v) => {
              return v.value !== "" || "Please select country";
            }
          }
        }}
        render={({ field: { onChange, value }, fieldState: { error } }) => {
          return (
            <>
              <Typography variant="body1">Country</Typography>
              <Select
                name="country"
                isClearable
                styles={
                  error && {
                    control: (provided, state) => ({
                      ...provided,
                      borderColor: "#fb6340",
                      borderRadius: "0.25rem"
                    }),
                    valueContainer: (provided, state) => ({
                      ...provided,
                      padding: "8px 8px"
                    })
                  }
                }
                onChange={(selectedOption) => {
                  resetField("state", {
                    defaultValue: { label: "", value: "" }
                  });
                  onChange(selectedOption);
                }}
                value={{ label: value?.label, value: value?.value }}
                options={countries}
              />
              {error?.message.length ? (
                <Typography variant="body2" color="#fb6340">
                  {error.message}
                </Typography>
              ) : null}
            </>
          );
        }}
      />
      <Controller
        name="state"
        control={control}
        rules={{
          required: true,
          validate: {
            isState: (v) => {
              return v.value !== "" || "Please select state";
            }
          }
        }}
        render={({ field, fieldState: { error } }) => {
          const countryDetails = watch("country");
          return (
            <>
              <Typography variant="body1">State</Typography>
              <Select
                name="state"
                isSearchable
                options={
                  countryDetails?.value.length
                    ? states[countryDetails.value]
                    : []
                }
                placeholder="Select state here"
                {...field}
                styles={
                  error && {
                    control: (provided, state) => ({
                      ...provided,
                      borderColor: "#fb6340",
                      borderRadius: "0.25rem"
                    }),
                    valueContainer: (provided, state) => ({
                      ...provided,
                      padding: "8px 8px"
                    })
                  }
                }
              />
              {error?.message.length ? (
                <Typography variant="body2" color="#fb6340">
                  {error.message}
                </Typography>
              ) : null}
            </>
          );
        }}
      />
Enter fullscreen mode Exit fullscreen mode

Here with resetField( reset individual fields ) and watch ( watch input value by passing the name of it ) APIs we can manage dependent input fields.

As you see in the above code, whenever we are changing country field we reset state field input values to default. With watch we can track the country field and update options for state field accordingly.

Form Validations

To add validations, we are using the rules prop that provides validation as - required, min, max, minLength, maxLength, pattern, validate. Any failed validations can be handled with error from the fieldState object, easy right❗️❗️

Form Submission and Reset

Now wrap these Controller Component in form element to manage form onSubmit event using handleSubmit and reset form to default values using reset functions.

  const { handleSubmit, reset, control, watch, resetField } = useForm({
    defaultValues
  });

  const onResetFormData = () => {
    reset(defaultValues);
  };
Enter fullscreen mode Exit fullscreen mode
<form onSubmit={handleSubmit((data) => console.log(data))}>
Enter fullscreen mode Exit fullscreen mode

So, once we click the submit button, our entered form data will show up in the console.

You can check out the whole implementation here in codesandbox

Why do we choose rhfπŸ‘©β€πŸ’»?

Few notable points,
🍬 UI library integrations
🍭 Render optimisation with isolated re-rendering
πŸ₯  Easy Form validations
🍯 Has TypeScript support
πŸͺ Embraces the hooks API, uncontrolled components
🍫 Minimum Bundle size

This was one way of implementing forms with react-hook-form. Let me know your thoughts on thisπŸ˜‡.

Top comments (4)

Collapse
 
bluebill1049 profile image
Bill

Thank you for writing this blog post. <3

Collapse
 
apoorvacg profile image
Apoorva CG

Hi Bill,
Happy you could drop by and read the post!
Thanks for rhf😊

Collapse
 
gzamann profile image
Gulshan Zaman Khan

Hey! great article πŸ‘
Are you familiar with Formik as well?
Which one of these do you think has a closer approach to native HTML forms and has more maintainable code?

Collapse
 
apoorvacg profile image
Apoorva CG

Hi,

i haven't worked closely with Formik library but react-hook-form gives support for uncontrolled components as well with register method to get 'ref' of each input elements.

react-hook-form has overall less boilerplate code to maintain.

thank u fr dropping by!!