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
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)
Thank you for writing this blog post. <3
Hi Bill,
Happy you could drop by and read the post!
Thanks for rhfπ
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?
Hi,
i haven't worked closely with Formik library but react-hook-form gives support for uncontrolled components as well with
registermethod to get 'ref' of each input elements.react-hook-form has overall less boilerplate code to maintain.
thank u fr dropping by!!