DEV Community

Cover image for Create Dynamic Forms in React Using React Hook Forms
Suresh Mohan for Syncfusion, Inc.

Posted on • Originally published at syncfusion.com on

Create Dynamic Forms in React Using React Hook Forms

Forms are one of the crucial parts of a web app. They help us to collect various data from the user, and it is the developer’s responsibility to validate all input data in the form fields in order to provide a good user experience.

Though new JavaScript libraries and frameworks like React have revolutionized the front-end ecosystem, maintaining and validating forms is still tedious. As developers, we always focus on increasing our productivity by reusing code as much as possible. It is always better to use a library that abstracts all these things by providing a simple wrapper, but adding too many third-party libraries can also have its drawbacks.

React Hook Form is a library that helps validate forms in React. It is very performant, adoptable, and super simple to use. It is also lightweight with no external dependencies, which helps developers achieve more while writing less code.

In this article, we will see how to use React Hook Form with third-party UI frameworks like Syncfusion and create a dynamic form in React.

Introduction

React Hook Form minimizes the number of re-renders, minimizes validate computation, and speeds up mounting. Rather than using states to control inputs, they use ref. Since React Hook Form uses ref, it is very well integrated with almost all the major UI libraries, as they support ref.

The package size is also very minimal. It is just 9.1KB minified and gziped as it has zero external dependencies. The APIs are very intuitive and provide a seamless working experience to developers. It also follows all the HTML standards for validating forms using constraint-based validation APIs.

You can start using React Hook Form by installing it from npm. Run the following command.

| npm install react-hook-form |

A simple form

In this section, we will explore how to create a simple form using React Hook Form along with Syncfusion React components. The useForm hook of React Hook Form is the core of its functionality.

In this form, we are going to have text fields, First Name and Last Name, two radio buttons, Male and Female, one dropdown for the profession, a checkbox for agreeing to the terms and conditions, and a Submit button.

Here, we are going to use a variety of fields so that we can explore native (input, radio, checkbox) as well as constructive inputs (dropdowns).

Import all these fields from Syncfusion.

import React from "react";
import { TextBoxComponent } from "@syncfusion/ej2-react-inputs";
import { DropDownListComponent } from "@syncfusion/ej2-react-dropdowns";
import {
 CheckBoxComponent,
 RadioButtonComponent,
 ButtonComponent,
} from "@syncfusion/ej2-react-buttons";
import "../node_modules/@syncfusion/ej2-base/styles/material.css";
import "../node_modules/@syncfusion/ej2-inputs/styles/material.css";
import "../node_modules/@syncfusion/ej2-react-dropdowns/styles/material.css";
import "../node_modules/@syncfusion/ej2-buttons/styles/material.css";
Enter fullscreen mode Exit fullscreen mode

Also, import useFrom and Controller from react-hook-form.

React Hook Form provides a wrapper component called Controller that allows you to register a controlled external component, similar to how the register method works. In this case, instead of the register method, we will use the control object from the useForm Hook.

import { useForm, Controller } from "react-hook-form";
Enter fullscreen mode Exit fullscreen mode

The useForm hook expects an object as input (defaultValues, reValidateMode, etc.) and returns an object containing a few properties. Let’s see a few of these that we are going to use in this example. Read the documentation to learn more about useForm.

The following are some of the props we will be using:

  • register : Registers an input field to the react-hook-form so that the input values can be tracked as well as validated. An important thing to note here is the input should have a unique name, which you can set either implicitly or explicitly within the register method. It works great with the native HTML inputs.
  • formState : This property contains information about the form’s state, such as its error state when a validation fails.
  • handleSubmit : As the name suggests, it manages form submission. We have to pass it as a prop to the onSubmit prop of the form component. This takes two functions as arguments. The first one is invoked when validations pass and returns the input values as a name-value pair. The second function is invoked when the validation fails and returns the name-error pair.
  • control : This object contains the methods for registering components in React Hook Form. This object is passed to the Controller component as a control prop along with the name props and rules.
  • watch : This method takes the input field name as input, watches it, and returns its values. It is useful for determining what to render.

The Controller component makes it easier to work with externally controlled components like DropDownList in the form. Read the documentation to learn more about Controller.

A few props that we will be using:

  • name (mandatory): A unique name that represents the input being controlled.
  • control : Control object returned from the useForm hook to register the component.
  • render : A function that returns a React element and provides the ability to attach events and values to the component. This simplifies integrating external controlled components with non-standard prop names.
  • rules : Validation rules for the input.
  • defaultValue : Default value of the input. It will override the defaultValue passed in the useForm hook.

This is how a controller component can be defined. In the render section, you can pass any component you wish and set its value, as well as update the form’s value by triggering field.onChange whenever value changes.

Read more about render in the React Hook Form documentation.

<Controller
           name="firstName"
           control={control}
           rules={{ required: true }}
           defaultValue=""
           render={({ field }) => (
             <TextBoxComponent
               placeholder="Enter your First Name"
               change={({ value }) => field.onChange(value)}
               value={field.value}
             />
           )}
         />
Enter fullscreen mode Exit fullscreen mode

There are two ways of defining default values and rules: together at the beginning, or individually for each input.

Both have their pros and cons, but I prefer defining them individually. This provides a better composition option.

For now, we are going to add simple validation like requiring a field, but you can also use complex validation rules like the following:

  • minLength and maxLength: Sets the minimum and maximum length of the string input value.
  • min and max: Sets the min and max range for the numerical input value.
  • type: Sets the type of the field. It can be email, number, text, or any other standard HTML input types.
  • pattern: A regular expression pattern.

To show the error, I have created an error component.

//Error Component
const Error = ({ children }) => <p style={{ color: "red" }}>{children}</p>;
Enter fullscreen mode Exit fullscreen mode

Initialize the useForm hook.

const {
   handleSubmit,
   control,
   watch,
   formState: { errors },
 } = useForm();
Enter fullscreen mode Exit fullscreen mode

Define all the input fields in the controlled component.

/* "handleSubmit" will validate your inputs before invoking "onSubmit" */
   <div className="wrapper">
     <form onSubmit={handleSubmit(onSubmit)}>
       <section>
         <label>First Name</label>
         {/* include validation with required or other standard HTML validation rules */}
         <Controller
           name="firstName"
           control={control}
           rules={{ required: true }}
           defaultValue=""
           render={({ field }) => (
             <TextBoxComponent
               placeholder="Enter your First Name"
               // floatLabelType="Auto"
               change={({ value }) => field.onChange(value)}
               value={field.value}
             />
           )}
         />
         {errors.firstName && <Error>This field is required</Error>}
       </section>
       <section>
         <label>Last Name</label>
         {/* include validation with required or other standard HTML validation rules */}
         <Controller
           name="lastName"
           control={control}
           rules={{ required: true }}
           defaultValue=""
           render={({ field }) => (
             <TextBoxComponent
               placeholder="Enter your Last Name"
               // floatLabelType="Auto"
               change={({ value }) => field.onChange(value)}
               value={field.value}
             />
           )}
         />
         {errors.lastName && <Error>This field is required</Error>}
       </section>
       <section>
         <label>Gender</label>
         {/* include validation with required or other standard HTML validation rules */}

         <Controller
           name="gender"
           control={control}
           rules={{ required: true }}
           defaultValue="female"
           render={({ field }) => (
             <div>
               <br />
               <RadioButtonComponent
                 label="Male"
                 value="male"
                 onChange={(value) => field.onChange(value)}
                 checked={field.value === "male"}
               />

               <RadioButtonComponent
                 label="Female"
                 value="female"
                 onChange={({ value }) => field.onChange(value)}
                 checked={field.value === "female"}
               />
             </div>
           )}
         />

         {errors.gender && <Error>This field is required</Error>}
       </section>
       <section>
         <label>Profession</label>
         {/* include validation with required or other standard HTML validation rules */}
         <Controller
           name="profession"
           control={control}
           rules={{ required: true }}
           defaultValue=""
           render={({ field }) => (
             <DropDownListComponent
               dataSource={[
                 "Frontend Developer",
                 "Backend Developer",
                 "Devops Engineer",
               ]}
               select={({ itemData }) => {
                 field.onChange(itemData.value);
               }}
               value={field.value}
             />
           )}
         />
         {errors.profession && <Error>This field is required</Error>}
       </section>

       <section>
         {/* include validation with required or other standard HTML validation rules */}
         <Controller
           name="agree"
           control={control}
           rules={{ required: true }}
           defaultValue={false}
           render={({ field }) => (
             <CheckBoxComponent
               label="I hereby agree to the terms."
               onChange={(e) => field.onChange(e.target.checked)}
               checked={field.value}
             />
           )}
         />
         {errors.agree && <Error>This field is required</Error>}
       </section>
       <div style={{ textAlign: "center" }}>
         <ButtonComponent type="submit" cssClass="e-success">
           Success
         </ButtonComponent>
       </div>
     </form>
   </div>
Enter fullscreen mode Exit fullscreen mode

When the form is submitted, handleSubmit will be invoked which will then invoke either the first or second argument based on the validation fulfillment.

const onSubmit = (data) => console.log(data);
Enter fullscreen mode Exit fullscreen mode

That’s it! You are done with a simple form in React. Working with React Hook Form makes it so easy.

A dynamic form

The web evolves at a tremendous speed and business requirements get more complex as a result. The web now delivers custom experiences based on users’ geographic locations and other requirements.

The same goes with forms. Having a dynamic form is what today’s developer must provide.

So, let’s see how to create a dynamic form in React using React Hook Form and Syncfusion.

Dynamic forms are generated through JavaScript from a JSON scheme. Thus, I have created the following simple schema which I will be using.

const dynamicForm = {
 firstName: {
   label: "First Name",
   type: "text",
   placeholder: "Enter your first name",
   defaultValue: "",
   rules: {
     required: true,
   },
 },
 lastName: {
   label: "Last Name",
   type: "text",
   placeholder: "Enter your last name",
   defaultValue: "",
   rules: {
     required: true,
   },
 },
 gender: {
   label: "Gender",
   type: "radio",
   options: ["male", "female"],
   defaultValue: "",
   rules: {
     required: true,
   },
 },
 profession: {
   label: "Profession",
   type: "dropdown",
   options: ["Front-end Developer", "Back-end Developer", "Devops Engineer"],
   defaultValue: "",
   rules: {
     required: true,
   },
 },
 agree: {
   type: "checkbox",
   label: "",
   checkboxLabel: "I hereby agree to the terms.",
   defaultValue: false,
   rules: {
     required: true,
   },
 },
};
Enter fullscreen mode Exit fullscreen mode

For simplicity, I have used nested objects, keys are the field names, and each field has its properties defined. The form contains different types of inputs for the sake of example.

Now that we have our schema ready. We are going to create an Input component that will generate the required inputs based on type.

This is a separate component which will return a Syncfusion component based on the properties received.

import React from "react";
import { TextBoxComponent } from "@syncfusion/ej2-react-inputs";
import { DropDownListComponent } from "@syncfusion/ej2-react-dropdowns";
import {
 CheckBoxComponent,
 RadioButtonComponent,
 ButtonComponent,
} from "@syncfusion/ej2-react-buttons";
import "../node_modules/@syncfusion/ej2-base/styles/material.css";
import "../node_modules/@syncfusion/ej2-inputs/styles/material.css";
import "../node_modules/@syncfusion/ej2-react-dropdowns/styles/material.css";
import "../node_modules/@syncfusion/ej2-buttons/styles/material.css";

const Input = ({ value, onChange, type, ...rest }) => {
 switch (type) {
   case "text":
     return (
       <TextBoxComponent
         placeholder={rest?.placeholder}
         change={({ value }) => onChange(value)}
         value={value}
       />
     );
   case "radio":
     return rest?.options.map((e) => (
       <RadioButtonComponent
         key={e}
         label={e}
         value={e}
         onChange={(value) => onChange(value)}
         checked={value === e}
       />
     ));
   case "dropdown":
     return (
       <DropDownListComponent
         dataSource={rest?.options}
         select={({ itemData }) => {
           onChange(itemData.value);
         }}
         value={value}
       />
     );

   case "checkbox":
     return (
       <CheckBoxComponent
         label={rest?.checkboxLabel}
         onChange={(e) => onChange(e.target.checked)}
         checked={value}
       />
     );

   default:
     return null;
 }
};

export default Input;
Enter fullscreen mode Exit fullscreen mode

The three most important things for any component are its type, value, and onChange handler. Apart from these, there are other things specific to certain input types.

Since a radio input always provides more than one option, we map the buttons to the array of options passed.

In the simple form example, we saw that the external UI components have to be wrapped inside the Controller component of the React Hook Form to make it work.

Thus for each field of the JSON, we have to generate the Controller component with an Input component.

const formInputs = Object.keys(dynamicForm).map((e) => {
   const { rules, defaultValue, label } = dynamicForm[e];

   return (
     <section key={e}>
       <label>{label}</label>
       <Controller
         name={e}
         control={control}
         rules={rules}
         defaultValue={defaultValue}
         render={({ field }) => (
           <div>
             <Input
               value={field.value}
               onChange={field.onChange}
               {...dynamicForm[e]}
             />
           </div>
         )}
       />
       {errors[e] && <Error>This field is required</Error>}
     </section>
   );
 });
Enter fullscreen mode Exit fullscreen mode

All the inputs from the JSON are generated dynamically. The last thing to do is render them inside the form and handle the submission.

const onSubmit = (data) => console.log(data);

 return (
   /* "handleSubmit" will validate your inputs before invoking "onSubmit" */
   <div className="wrapper">
     <form onSubmit={handleSubmit(onSubmit)}>
       {/* render the form inputs */}
       {formInputs}
       <div style={{ textAlign: "center" }}>
         <ButtonComponent type="submit" cssClass="e-success">
           Success
         </ButtonComponent>
       </div>
     </form>
   </div>
 );
Enter fullscreen mode Exit fullscreen mode

We have successfully generated a dynamic form in React using React Hook Form and Syncfusion components!

Resource

To try this demo yourselves, you can refer to the Dynamic Form in React Using React Hook Forms project on GitHub.

Conclusion

I hope you enjoyed reading this article and learning about dynamic form creation in React using React Hook Forms. I hope you’ll create a dynamic form in React yourself. Kindly share your feedback about this article in the comment section below.

Syncfusion’s React UI component library is the only suite that you will ever need to build an application. It contains over 65 high-performance, lightweight, modular, and responsive UI components in a single package. Download the free trial and evaluate the controls today.

If you have any questions or comments, you can contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!

Related blogs

Top comments (0)