Dynamic user forms with React Hooks

Esteban Aristizábal

Software Developer @ Kushki

November 16, 2020

9 min read

The creation of forms that will capture information from an end-user can be a difficult task because it must take many factors into account, such as: validation of fields, accessibility, maintainability, extensibility, and subject to change, to achieve it in the shortest possible time, with good practices, among others. And it can be more challenging in the case that the form that we wish to develop is extensive, complex, with numerous sections, or involving generating new forms according to user choices.

In Kushki, we perform this work continuously to offer the best user experience in the use of our platform. In this article, we will tell you how we manage to solve all these difficulties for an optimal result in the development of user forms.

Kushki React Form

What are the technologies and libraries to be used?

It is a library for the construction of user interfaces, which mainly offers the possibility of developing complex web applications with data that changes through time. It adapts very well to our needs to constantly collect information in our different products to update them in our platform, in addition to presenting information that continuously changes in an immediate and consistent way. Some significant characteristics it also offers are: Ease of use, reusability of graphic components, and efficiency when constructing complex interfaces starting from simpler pieces.

They are a new addition to React, allowing to write applications with a functional approach, reducing the amount of code necessary, simplifying the complex components previously developed with classes, to a simpler structure, which allows to abstract and reuse logic in these functions, to achieve components easier to understand and maintain.

It is a programming language that compiles JavaScript, and the reason to use it, is that JavaScript was not thought for the creation of large and complex systems, but for the implementation of dynamic functionalities to a website; that’s why Typescript becomes that fundamental piece for JavaScript to be highly scalable, maintaining a great level of flexibility. When adding strict typing, we achieve a more robust and solid code, allowing our applications to have fewer errors, being easier to test, and greater maintainability through time, so our adoption of this programming language is very high in our set of applications.

It is a library for the construction of forms with React, created with the objective of achieving greater performance and ease when implementing and validating forms.

Some reasons why it is recommended to use React Hook Form are:

  • It is intuitive, allowing the developer to use a single function responsible for abstracting the form’s entire handling logic.
  • It uses Hooks, getting a neater code when reusing functions responsible for the management of the different parts of the interface.
  • It offers good performance, since it minimizes the number of re-rendering when using Hooks, and isolation from the form fields at the time their status changes.
  • It is light, without any dependence that affects its size when downloading it.
  • It has built-in validation; it is in charge of the control of errors that happen when entering erroneous data in the fields in the form.
  • Integration with Typescript, to maintain a strict typing of the fields that must be collected from the user.

With some disadvantages:

  • It requires the use of functional components, so it is incompatible with components made with classes.
  • The use of Hooks, as it is a new feature in React, can assume an additional time to learn to use them correctly.

How should it be implemented?

React Hook Form is the library used for the construction of the form. In this section, we will focus on describing the development process to carry it out, together with technical explanations that allows to better understand how its adoption can proceed in a project.

1. Define strict form typing

When specifying an interface with the fields the form will have, allows us to maintain an understanding in the working team, providing error detection in compilation time. Below we have an implementation of a simple form interface, that will collect data from a customer.

typescript interface IForm { clientName: string; clientDetails: { email: string; documentType: string; documentNumber: string; } }

2. Initializes the form with the function useForm()

This function returns the methods with which interaction with the API library will take place; it receives a generic typing with the interface that we previously defined, to maintain a strict typing of the form, and we also specified that field validation is with the “onBlur” mode, that is to say, at the moment the element loses the user approach.

typescript export const FormComponent: React.FC = () => { const form = useForm ({ mode: “onBlur”, }); }

3. Wraps the sections of the form with the FormProvider component

This component offered by the library, makes use of React’s Context API, which solves the problem of passing “props” at each level of the component tree, this is specifically useful in complex forms with multiple nested sections. For the implementation, we have defined that the form will have 2 nested sections, the first will capture the customer name, and the second the customer details. In addition to specifying the “handleSubmitForm” function, which will be responsible for processing the data once the user makes a sending of the form.

const handleSubmitForm: SubmitHandler= async (formData) => { // Save fields received from the form };

return (

 <ClientNameSection/> <ClientDetailsSection/> <Button style={{ margin: 20 }} variant="contained" color="primary" disableElevation onClick={form.handleSubmit(handleSubmitForm)} > Save </Button> </FormProvider>
); }; ``

**4.** Use the [ConnectForm](https://react-hook-form.com/advanced-usage#ConnectForm) component for nested sections of the form

It is very common to develop forms that have deeply nested sections within the component tree; in this case, the ConnectForm component is very well integrated, allowing for wrapping the nested component with the library methods without the need to obtain them from the "props." Here we use React's ["renderProps"](https://es.reactjs.org/docs/render-props.html#gatsby-focus-wrapper) technique, to reuse this component in multiple parts of the code.

```typescript const ConnectForm =({ children, }: { children: (form: UseFormMethods) => JSX.Element; }) => { const formMethods = useFormContext();

return children({ ..formMethods, }); }; ```

**5.** In the nested sections of the form, it uses the [TypedController component](https://react-hook-form.com/advanced-usage#StrictlyTyped)

This library component will be in charge of registering the element in the form status to track user entries. We will use the "Textfield" component of the [Material-UI](https://material-ui.com/es/) library, which is wrapped within the TypedController in its "render" attribute. Two nested sections, called "ClientNameSection" and "ClientDetailsSection" will be created:

```typescript export const ClientNameSection: React.FC = () => { return ( <ConnectForm\> {({ control, errors }) => { const TypedController = useTypedController({ control: control, });

return ( <div style={{ padding: 20 }}> <TypedController name={"clientName"} rules={{ required: true }} render={(props) => ( <TextField {...props} label="Customer name" variant="outlined" error={!! errors.clientName} required fullWidth margin="normal" helperText={ !! errors.clientName && "Required field" } /> ) } /> </div> ); }} </ConnectForm>
typescript export const ClientDetailsSection: React.FC = () => { return ( <ConnectForm\> {({ control, errors }) => { const TypedController = useTypedController({ control: control, });

return ( <div style={{ padding: 20 }}> <div> <Typography variant="h6" color="primary"> Customer details </Typography> <TypedController name={["clientDetails", "email"]} rules={{ required: true, pattern: emailPattern }} render={(props) => ( <TextField {...props} label="email" variant="outlined" error={!! errors.clientDetails?.email} required fullWidth margin="normal" helperText={ !! errors.clientDetails?.email && "Invalid email" } /> ) } /> </div> <div style={{ display: "flex" }}> <div style={{ width: "50%", marginRight: 10 }}> <FormControl variant="outlined" fullWidth margin="normal" > <InputLabel>Type of document</InputLabel> <TypedController name={["clientDetails", "documentType"]} defaultValue={"CI"} render={(props) => ( <Select {...props} label="Type of document"> <MenuItem key="CI" value="CI"> {"CI"} </MenuItem> <MenuItem key="RUC" value="RUC"> {"RUC"} </MenuItem> <MenuItem key="PAS" value="PAS"> {"PAS"} </MenuItem> </Select> ) } /> </FormControl> </div> <div style={{ width: "50%", marginLeft: 10 }} > <TypedController name={["clientDetails", "documentNumber"]} render={(props) => <TextField {...props} id="documentNumber" label="Document number" variant="outlined" error={!! errors.clientDetails?.documentNumber} fullWidth margin="normal" /> ) } /> </div> </div> </div> ); }} </ConnectForm>
); }; ``

In addition, the "TypedController" component maintains a strict typing of the form, as we observe in the following code section, when placing a wrong argument in the "name" attribute, the compiler detects an error in running time.


We can see how it would be visualized in the browser with the 2 nested sections of the form.


**Observe** the changes in the form fields with the [useWatch()](https://react-hook-form.com/api#useWatch) function

In the development phase, the need to perform actions according to user entries, for example, to conditionally render, or to validate data in real-time; the useWatch() function, allows us to be listening to the changes of a field of the form, and to act according to it. In our implementation, the function can be used to validate if the "email" field exists at the moment the user enters it in the form:

```typescript export const ClientDetailsSection: React.FC = () => { return ( <ConnectForm\> {({ control, errors }) => { const TypedController = useTypedController({ control: control, });

const email = useWatch({ name: "email" }) as string; useEffect(() => { // Verificar if email already exists }, [email]); ...

Send the values of the form with the handleSubmit() function

This function will pass the collected data, once a successful validation is performed, to be able to save the form data.

const handleSubmitForm: SubmitHandler < IForm > = async (formData) => { // Save fields received from the form }; ...

Prior to the invocation of the function, a validation of the fields is performed, as we can observe in the following image, the library is in charge of detecting the errors and updating them in the interface.

Finally, with the full fields, we can see how they will be received in the function to be able to process them:

json { “clientName”: “John Doe”, “clientDetails”: { “email”: “jhon.doe@test.com”, “documentType”: “CI”, “documentNumber”: “1764537289” } }

What are our results?

Currently, in Kushki, this implementation has allowed us to:

  • Build forms in a more efficient way by reducing the written code.
  • Extensive and complex forms are developed from simple components, and the responsibility of validating and controlling them is delegated to the library.
  • Having a standardization in developing them, to achieve better maintainability of the code throughout time within the working team.
  • Achieve performance improvements by minimizing the number of renderings that the browser must make.
  • Detecting errors before the code is in production thanks to the strict typing of the form.

Also, it is important to consider these observations before adapting it in a project.

  • There will be an additional learning time until the working team properly implements the library.
  • It can be an excessive solution if the form to be developed is simple and with a few elements.
  • The library has fewer community and collaborators than other older libraries in the market, such as Formik or Redux Form. You can see a complete comparison with these libraries in this link.

Finally, I hope that this article has been of help in the project that you are carrying out or planning to do, and that you can adopt this way of constructing forms, so that you have an optimal and efficient development process.

Would you like to keep up to date with our content? Subscribe to our mailing list.