React Hook Form with Controllers
Tags: reacthookfrom, forms, reactselect, materialui
Published: 2022-06-24 02:07:51

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
```
or

```
npm install react-hook-form
```


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
```

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>);
}
```

`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" : ""}
                 />
               </>
              );
         }}
 />
```
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}
            </>
          );
        }}
      />
```
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);
  };
```

```
<form onSubmit={handleSubmit((data) => console.log(data))}>
```

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](https://codesandbox.io/s/react-hook-form-v7-controller-z4js1o?file=/src/index.js)


#### 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😇.