DEV Community

Cover image for Build forms using React-Hook-Form and ChakraUi
Wonder2210
Wonder2210

Posted on

Build forms using React-Hook-Form and ChakraUi

Once of the common tasks as a react developer you will encounter is, build forms

That means that if you wanna do it the proper way you have to validate the fields, handle the state of the fields , errors, types etc, things will get complex

And as you introduce more complexity to your forms the more difficult develop will be, This is where React-Hook-Form kicks in

Tools we are going to use :

  • Node (version 16 or greater)
  • Yarn

Set Up:

We are going to be using create react app for this project, but whichever frontend react tooling you like is good to go

initialize your project with :
yarn create react-app rhf-tut
then:
cd rhf-tut

Install dependencies:

Chakra Ui:
yarn add @chakra-ui/react @emotion/react@^11 @emotion/styled@^11 framer-motion@^6
React Hook form + Yup:
yarn add react-hook-form @hookform/resolvers yup

Usage of the React hook form:

Basically we import the useForm hook from react-hook-form, this hook will return us, some functions like

  • register:this 'register' each of the inputs will use , it basically returns props needed to control the input)

  • handleSubmit: This function is going to be passed to the onSubmit of the form, and it will have as parameter a function that will get the data of our form

  • formState: It will held the state of our form , values like the errors, if the form was successFully submitted , or if the form is dirty

There is some other functions that we will be using later, but basically these are the most commonly used

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

const UserForm = () => {
  const {register, handleSubmit, formState: { errors }} = useForm();
  return (
    // Inputs code here
  );
};

export default UserForm;
Enter fullscreen mode Exit fullscreen mode

Text Inputs:

As i told before, register function is meant to 'register' our inputs, so let's import Input from chakra-ui and pass down on props {...register('textInput')} using as argument of register the name for our input :

import { Input } from '@chakra-ui/react';
import { useForm } from "react-hook-form";

const UserForm = () => {
  const {register, handleSubmit, formState: { errors }} = useForm();
  return (
    <Input {...register('textInput')} />
  );
};

export default UserForm;
Enter fullscreen mode Exit fullscreen mode

Radio Inputs:

For radio inputs , we are going to do something a little bit different, since the RadioGroup component of chakra-ui (which is the one that handles the onChange and value ) is not an input so , if we use the register function won't work , we'll have to use the <Controller /> component from react-hook-form that gives us a little bit more control over our inputs

import { Radio, RadioGroup, Stack } from '@chakra-ui/react';
import { useForm, Controller } from "react-hook-form";

const UserForm = () => {
  const {register, handleSubmit, formState: { errors }, control} = useForm();
  return (
    <Controller
          name="radio"
          control={control}
          render={({ field: { onChange, value } }) => (
            <RadioGroup onChange={onChange} value={value}>
              <Stack direction="row">
                <Radio value="1">First</Radio>
                <Radio value="2">Second</Radio>
                <Radio value="3">Third</Radio>
              </Stack>
            </RadioGroup>
          )}
        />
  );
};

export default UserForm;
Enter fullscreen mode Exit fullscreen mode

Checkbox Inputs:

For checkbox we can use the register function as we do with normal text input but this time adding the value attribute , as you can notice this time we use the register function passing the same name.

import { Radio, RadioGroup, Stack } from '@chakra-ui/react';
import { useForm, Controller } from "react-hook-form";

const UserForm = () => {
  const {register, handleSubmit, formState: { errors }, control} = useForm();
  return (
 <FormControl>
        <FormLabel>Choose many</FormLabel>
        <Stack direction="row">
          <Checkbox value="1" {...register('multiple')} >
            Here
          </Checkbox>
          <Checkbox value="2" {...register('multiple')} >
            Here
          </Checkbox>
        </Stack>
      </FormControl>
  );
};

export default UserForm;
Enter fullscreen mode Exit fullscreen mode

Handling submit

Now comes the time we need to get the data from our form to send it to our backend or any other action we would like to do with the data of our form,

const UserForm = () => {
  const {register, handleSubmit, formState: { errors }} = useForm();
  const onSubmit = (formData) => apiCallToSaveData(formData);
  const onInvalid = () => alert('This form is invalid try again'); 
  return (
    <VStack
      as="form"
      minWidth="30%"
      bgColor="#FFF"
      padding="2em"
      borderRadius="12px"
      shadow="md"
      mt="4em"
      onSubmit={handleSubmit(onSubmit, onInvalid)}
    >
      // here some inputs
    </VStack>

  );
};
Enter fullscreen mode Exit fullscreen mode

handleSubmit will take our onSubmit function and optionally onInvalid function as parameters , it will pass to onSubmit all the data in our form , and onInvalid , will be executed just in case our form is invalid, that's our next topic

Validation

To verify the data in our form with react hook form we are going to use Yup, an awesome tool for schema validation , this is my favorite approach, and one of the things i love the most of React hook form, because you just have to describe the way your data looks, with its types, if it is required or not, length, etc

We define our schema of types:

import * as yup from "yup";
const formSchema = yup
  .object({
    name: yup.string(),
    email: yup
      .string()
      .email("Please introduce a valid email")
      .required("Email is required"),
    phoneNumber: yup
      .string()
      .matches(phoneRegExp, "It doesn't seem to be a phone number")
      .length(11, "Phone number is too short"),
    multiple: yup
      .array()
      .of(yup.string())
      .ensure()
      .compact()
      .min(1, "I bet you like one of those").required(),
    radio: yup.string(),
  })
  .required();
Enter fullscreen mode Exit fullscreen mode

as you can see we pass a string as parameter on some functions inside the schema, like in required or email, that's the text we are going to display in case that The data in our form doesn't match that property of our schema,
And add it to RHF :

import { yupResolver } from "@hookform/resolvers/yup";
...
const {
    register,
    handleSubmit,
    formState: { errors },
    control,
  } = useForm({
    resolver: yupResolver(formSchema),
  });
Enter fullscreen mode Exit fullscreen mode

Display errors:

As you can notice above we are getting errors from formState, this will have the errors found in the form, we can access to them using the same name we assigned to the input, we are going to add a proper error info to our form like this:

....
<FormControl isInvalid={errors?.email}>
        <FormLabel htmlFor="email">Email address</FormLabel>
        <Input type="text" {...register("email")} />
        {errors?.email ? (
          <FormErrorMessage>
            {errors.email.message}
          </FormErrorMessage>
        ) : (
          <FormHelperText>We'll never share your email.</FormHelperText>
        )}
      </FormControl>
.....
Enter fullscreen mode Exit fullscreen mode

the message will be the one defined on our schema (For me that's so awesome, i love it), and if you need the type of the error, just error.email.type(it will depend on the schema of yup), in case you need to execute a different action than show the error message while error

Thanks for reading,i hope it's useful for you, you can leave any suggestion or doubt about it in the comments box below 👇, also you can find the Code used for this Post here

Top comments (0)