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
register
method to get 'ref' of each input elements.react-hook-form has overall less boilerplate code to maintain.
thank u fr dropping by!!