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.
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
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";
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";
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:
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:
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} /> )} />
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:
To show the error, I have created an error component.
//Error Component const Error = ({ children }) => <p style={{ color: "red" }}>{children}</p>;
Initialize the useForm hook.
const { handleSubmit, control, watch, formState: { errors }, } = useForm();
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>
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);
That’s it! You are done with a simple form in React. Working with React Hook Form makes it so easy.
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, }, }, };
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;
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> ); });
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> );
We have successfully generated a dynamic form in React using React Hook Form and Syncfusion components!
To try this demo yourselves, you can refer to the Dynamic Form in React Using React Hook Forms project on GitHub.
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 a wide range of 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!