Dynamic forms with React Hook Form. 📝
Tags: javascript, react, tutorial, webdev
Published: 2023-03-10 13:46:39

This time, we will again create dynamic forms but now with the help of the [**react-hook-form**](https://react-hook-form.com/) library.

> Note 📣: **You need to have knowledge in Typescript to follow this tutorial, as well as React JS**.

You might be interested in this article, where we also do the same as in this post, but using the [**Formik**](https://formik.org/) library. 😉

{% embed https://dev.to/franklin030601/dynamic-forms-with-formik-and-react-js-3no1 %}

&nbsp;

## Table of contents.
> 📌 [Technologies to be used.](#1)  
> 📌 [Creating the project.](#2)  
> 📌 [First steps.](#3)  
> 📌 [Creating the form object.](#4)  
>> 📌 [Creating the typing for the inputs.](#5)  
>> 📌 [Now we create the form object with the help of the typing.](#6)  
>
> 📌 [Creating the validation schema for our form.](#7)  
> 📌 [Function to generate the inputs.](#8)  
> 📌 [Creating the form component.](#9)  
> 📌 [Creating the components of each input.](#10)  
> 📌 [Using our Form component.](#11) 
> 📌 [Conclusion.](#12)  
>> 📌 [Demo.](#13)  
>> 📌 [Source Code.](#14)  

&nbsp;


## 💊 Technologies to be used. <a id="1"></a>
- React JS **18.2.0**
- TypeScript **4.9.3**
- React Hook Form **7.43.0**
- Vite JS **4.1.0**
- Tailwind CSS **3.2.4** (neither the installation nor the configuration process is displayed).

## 💊 Creating the project. <a id="2"></a>

We will name the project: **`dynamic-forms-rhf`** (optional, you can name it whatever you like).
```bash
npm create vite@latest
```
We create the project with Vite JS and select React with TypeScript.  
Then we run the following command to navigate to the directory just created.

```bash
cd dynamic-forms-rhf
```
Then we install the dependencies.
```bash
npm install
```
Then we open the project in a code editor (in my case VS code).
```bash
code .
```

## 💊 First steps. <a id="3"></a>

Inside the **src/App.tsx** file we delete everything and create a component that displays a `hello world`.
```tsx
const App = () => {
    return (
        <div>Hello world</div>
    )
}
export default App
```

> 🚨 Note: Each time we create a new folder, we will also create an **index.ts** file to group and export all the functions and components of other files that are inside the same folder, so that these functions can be imported through a single reference, this is known as **barrel file**. 


Let's create a layout, create a folder **src/components** and inside create a file **Layout.tsx**.

```tsx
export const Layout = ({ children }: { children: JSX.Element | JSX.Element[] }) => {
	return (
		<>
			<h1 className='text-center my-10 text-5xl'>
				<span>Dynamic Form</span>
				<span className='font-bold bg-clip-text text-transparent  text-[#EC5990]'>
					{' - '}
					React Hook Form
				</span>
			</h1>

			<main className='grid sm:grid-cols-2 grid-cols-1 sm:mb-0 mb-10 gap-10 place-items-start justify-items-center px-5'>
				{children}
			</main>
		</>
	)
}
```

Now, inside the **src/App.tsx** file, we add the layout.

```tsx
import { Layout } from './components'

const App = () => {

	return (
		<Layout>
			<span>Form</span>
		</Layout>
	)
}
export default App
```
Then we are going to install the necessary packages.
- **react-hook-form**, to handle the forms in an easier way.
- **yup**, to handle form validations.
- **@hookform/resolvers**, to integrate yup with react-hook-form.

```bash
npm install -E react-hook-form @hookform/resolvers yup
```

> Previously I had already done this same exercise of dynamic forms but using the Formik library, and the truth is very similar to what we are going to do, the only thing to change are the components such as the form and inputs.
>


## 💊 Creating the form object. <a id="4"></a>

### 💊 Creating the typing for the inputs. <a id="5"></a>
First, let's create the typing. We create a new folder **src/types** and create the index.ts file.

Now first we create the interface for the inputs, which can have even more properties, but these are enough to make this example.

The highlights are the last three properties of the **InputProps** interface:
- **typeValue**: necessary since we need to tell Yup what type of value the input accepts.
- **validations**: validations that will be set to Yup based on the input; I only put basic validations, although you can integrate more if you look in the Yup documentation. 
	- The validation that may be more complicated for you may be **oneOf**, if you have not used Yup. This validation needs a reference or the name of another input to validate if both inputs contain the same content. An example of where to use this validation is in an input where you create a password and another one where you have to repeat password and both values have to match. 
- **options**: this property is necessary only if the input is a select or a group of radio type inputs.

```tsx
export interface InputProps {
    type: 'text' | 'radio' | 'email' | 'password' | 'select' | 'checkbox'
    name: string
    value: string | number | boolean
    placeholder?: string
    label?: string

    typeValue?:  'boolean' | 'number'
    validations?: Validation[]
    options?: Opt[]
}

export interface Opt {
    value: string | number
    desc: string
}

export interface Validation {
    type: 'required' | 'isEmail' | 'minLength' | 'isTrue' | 'oneOf'
    value?: string | number | boolean
    message: string
    ref?: string
}
```
Also at once we create this type for the types of forms we are going to develop.
**In this case we are only going to create two forms.**

```tsx
export type FormSection = 'register' | 'another'
```

### 💊 Now we create the form object with the help of the typing. <a id="6"></a>

Thanks to Typescript we can create our forms in this object.
We create a new folder **src/lib** and inside we create the file **form.ts** and add the following:

```tsx
import { FormSection, InputProps } from '../types';

export const forms: { [K in FormSection]: InputProps[] } =
{

    register: [
        {
            label: "New username",
            type: "text",
            name: "username",
            placeholder: "New username",
            value: "",
            validations: [
                {
                    type: "minLength",
                    value: 3,
                    message: "Min. 3 characters",
                },
                {
                    type: "required",
                    message: "Username is required"
                },
            ],

        },
        {
            label: "New Password",
            type: "password",
            name: "password",
            placeholder: "New password",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Password is required"
                },
                {
                    type: "minLength",
                    value: 5,
                    message: "Min. 5 characters",
                }
            ],

        },
        {
            label: 'Repeat your password',
            type: "password",
            name: "repeat_password",
            placeholder: "Repeat password",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Repeat password is required"
                },
                {
                    type: "minLength",
                    value: 5,
                    message: "Min. 5 characters",
                },
                {
                    type: 'oneOf',
                    message: 'Passwords must match',
                    ref: 'password'
                }
            ],

        },

    ],

    another: [

        {
            label: "E-mail address",
            type: "email",
            name: "email",
            placeholder: "correo@correo.com",
            value: "",
            validations: [
                {
                    type: "required",
                    message: "Email is required"
                },
                {
                    type: "isEmail",
                    message: "Email no valid"
                }
            ],

        },
        {
            type: "select",
            name: "rol",
            label: "Select an option: ",
            value: "",
            options: [
                {
                    value: "admin",
                    desc: "Admin",
                },
                {
                    value: "user",
                    desc: "User"
                },
                {
                    value: "super-admin",
                    desc: "Super Admin"
                }
            ],
            validations: [
                {
                    type: "required",
                    message: "Rol is required"
                }
            ]
        },
        {
            type: "radio",
            name: "gender",
            label: "Gender: ",
            value: "",
            options: [
                {
                    value: 'man',
                    desc: "Man"
                },
                {

                    value: "woman",
                    desc: "Woman"
                },
                {

                    value: "other",
                    desc: "Other"
                },
            ],
            validations: [
                {
                    type: "required",
                    message: "Gender is required"
                }
            ]
        },
        {
            type: "checkbox",
            name: "terms",
            typeValue: "boolean",
            label: "Terms and Conditions",
            value: false,
            validations: [
                {
                    type: "isTrue",
                    message: "Accept the terms!"
                }
            ]
        },
    ]
}
```



## 💊 Creating the validation schema for our form. <a id="7"></a>
Let's create a new file in src/lib and name it **getInputs.ts**.
We create a new function to generate the validations to each input.
This function receives the fields, and each field is of type InputProps. We are also going to create 2 types only so that Typescript does not bother us later.

> Note that we created the types YupBoolean and YupString. If you want you can add other types either to handle some other data type like numeric or array. For example:
>```tsx
>   type YupNumber = Yup.NumberSchema<boolean | undefined, AnyObject, number | undefined>
>```
> I don't add it, because in my interfaces I don't handle any validation of type number or array.


```tsx
import * as Yup from "yup";
import { AnyObject } from "yup/lib/types";
import { FormSection, InputProps } from '../types';
import { forms } from '../lib';

type YupBoolean = Yup.BooleanSchema<boolean | undefined, AnyObject, boolean | undefined>
type YupString = Yup.StringSchema<string | undefined, AnyObject, string | undefined>

const generateValidations = (field: InputProps) => {}
```

First we create a variable that will be initialized with the datatype that will handle our input. The datatype we obtain it from the **typeValue** property, in case it is *undefined* by default the datatype will be *string*, and then we execute the function

```tsx
let schema = Yup[field.typeValue || 'string']()
```
Then we are going to go through the validations of the field, since it is an array. 

Within the loop, we will use a switch case, evaluating what type of rule the field has.

```tsx
const generateValidations = (field: InputProps) => {

    let schema = Yup[field.typeValue || 'string']()

    for (const rule of field.validations) {

        switch (rule.type) { }
    }
}
```

In each case of the switch we will overwrite the schema variable. In the following way:

If it has an 'isTrue' validation it means that the input handles Boolean values, so we want our schema to behave as a YupBoolean, otherwise Typescript would be complaining. Then we execute the function that has to do with each case. 

For example, in the case of 'isTrue', we execute the function with the exact same name, and inside we pass the message

```tsx
case 'isTrue'   : schema = (schema as YupBoolean).isTrue(rule.message);  break;
```

In the case that the validation is oneOf, we need to send it, as first parameter an array and as second parameter a message.

In the case of the array, it must be the value you want to match, but in this case we want to match the value of another field, so we use **Yup.ref** which needs a string that refers to the **name** attribute of an input.
So that when the validation is done, it checks if both fields contain the same value.

```tsx
case 'oneOf'    : schema = (schema as YupString)
                                            .oneOf(
                                                    [ Yup.ref(rule.ref as string) ], 
                                                    rule.message
                                                  ); 
break;
```
This is how our first function would look like. At the end we return the variable **schema**.
Note that at the beginning of the function, we place a condition where if the field has no validations then return null and avoid executing the cycle.

```tsx
import * as Yup from "yup";
import { AnyObject } from "yup/lib/types";
import { FormSection, InputProps } from '../types';
import { forms } from '../lib';

type YupBoolean = Yup.BooleanSchema<boolean | undefined, AnyObject, boolean | undefined>
type YupString = Yup.StringSchema<string | undefined, AnyObject, string | undefined>

const generateValidations = (field: InputProps) => {

    if (!field.validations) return null

    let schema = Yup[field.typeValue || 'string']()

    for (const rule of field.validations) {
        switch (rule.type) {
            case 'isTrue'   : schema = (schema as YupBoolean).isTrue(rule.message);  break;
            case 'isEmail'  : schema = (schema as YupString).email(rule.message);  break;
            case 'minLength': schema = (schema as YupString).min(rule?.value as number, rule.message);  break;
            case 'oneOf'    : schema = (schema as YupString).oneOf([Yup.ref((rule as any).ref)], rule.message);  break;
            default         : schema = schema.required(rule.message);  break;
        }
    }

    return schema
}
```

## 💊 Function to generate the inputs. <a id="8"></a>

First we are going to create a function and we name it getInputs, which is of generic type and receives as parameter the **section** (that is to say that form you want to obtain its fields, in this case it can be the form of signUp or the other one).

We are going to create two variables that we will initialize them as empty objects and that at the end will have to contain new properties.

```tsx

export const getInputs = <T>(section: FormSection) => {

    let initialValues: { [key: string]: any } = {};

    let validationsFields: { [key: string]: any } = {};

};
```
Inside the function we will make a for of loop. In which we are going to go through the fields of a specific form.

1. Inside the cycle, we are going to compute the values in the initialValues variable, and to compute the values we use the name property of the field.

2. We verify if there are validations for the field.
    - If there are no validations, then continue with the next field.
    - If there are validations, we execute the function that we created before **generateValidations** sending the field as argument.

3. Then to the validationsFields variable, we also compute the values using the name property of the field, and we assign the validation schema that has been generated.

```tsx
for (const field of forms[section]) {

    initialValues[field.name] = field.value;

    if (!field.validations) continue;

    const schema = generateValidations(field)

    validationsFields[field.name] = schema;
}
```

Once the cycle is finished, we must return 3 properties.
- The validation schema inside a **Yup.object**, spreading the **validationsFields** properties.
```tsx
validationSchema: Yup.object({ ...validationsFields }),
```
- The initial values, and we will make them behave as generic so that we can use them afterwards
```tsx
initialValues: initialValues as T,
```
- The fields that we want to show in our form.
```tsx
inputs: forms[section]
```
This is what our function will look like at the end
```tsx

export const getInputs = <T>(section: FormSection) => {

    let initialValues: { [key: string]: any } = {};

    let validationsFields: { [key: string]: any } = {};

    for (const field of forms[section]) {

        initialValues[field.name] = field.value;

        if (!field.validations) continue;

        const schema = generateValidations(field)

        validationsFields[field.name] = schema;
    }

    return {
        validationSchema: Yup.object({ ...validationsFields }),
        initialValues: initialValues as T,
        inputs: forms[section],
    };

};

```

## 💊 Creating the form component. <a id="9"></a>
First we are going to prepare the interface for the props that our Form component is going to receive.

- **onSubmit**, function that executes the form.
- **labelButtonSubmit**, text that will show the button.
- **titleForm**, text that will show the form.

The last 3 properties are what returns the function that we did to generate the inputs and their validations.
```tsx
interface Props {
	onSubmit: (data: unknown) => void
	labelButtonSubmit?: string
	titleForm?: string

	initialValues: unknown
	validationSchema: SchemaForm
	inputs: InputProps[]
}
```

The validationSchema property is of type **SchemaForm**.
```tsx
// src/types/index.ts
export type SchemaForm = OptionalObjectSchema<{
    [x: string]: any;
}, AnyObject, TypeOfShape<{
    [x: string]: any;
}>>
```
Now we create the component, and inside we destructure the props that the component receives.

Then we use the hook of useForm, which we are going to establish an object as argument, we access to the property:
 - **resolver**, to set the validation scheme, for this we use the function **yupResolver** and we pass as argument the **validationSchema** that comes by props.
 - **defaultValues**, to establish the default values and we will assign the props of **initialValues**.

Note that we do not destructure anything of the **useForm** hook.

```tsx
import { yupResolver } from '@hookform/resolvers/yup'
import { useForm } from 'react-hook-form'

export const Form = ({ ...props }: Props) => {
	const {
		initialValues,
		inputs,
		onSubmit,
		validationSchema,
		titleForm,
		labelButtonSubmit = 'Submit'
	} = props

	const formMethods = useForm({
		resolver: yupResolver(validationSchema),
		defaultValues: { ...(initialValues as any) }
	})

	return (
		<></>
	)
}
```
Next, we are going to use a component that offers us react-hook-form, which is the **FormProvider** and we are going to spread the **formMethods** of the **useForm** hook.

The **FormProvider** will help us to communicate the state of the form with the components (inputs) that are nested inside the FormProvider. With the purpose of separating the components and not having everything in the same file.

Inside the FormProvider we will place a form and in the onSubmit method of the form label, we are going to execute a property of the formMethods, which is the **handleSubmit**, and as argument we pass the onSubmit that receives the component Form by props. 

This handleSubmit will only be executed if there are no errors in each input, and when it is executed it will return the values of each input.

```tsx
import { FormProvider, useForm } from 'react-hook-form'

// interface

export const Form = ({ ...props }: Props) => {
    // props

	const formMethods = useForm({
		resolver: yupResolver(validationSchema),
		defaultValues: { ...(initialValues as any) }
	})

	return (
		<FormProvider {...formMethods}>
			<form
				onSubmit={formMethods.handleSubmit(onSubmit)}
				className='bg-secondary rounded-md p-10 pt-5 shadow-2xl shadow-primary/30 flex flex-col gap-2 border border-primary w-full min-h-[390px]'
			>
                <section className='flex-1 flex flex-col gap-3'>
                    {/* inputs here */}
                </section>
			</form>
		</FormProvider>
	)
}
```

Now we are going to create a function, to return the different types of inputs.
We use the prop **inputs** that we destructured of the props that the Form component receives.


Based on the type of input we are going to render one or another input.

Note that we are using components that we have not yet created. Also note, that from the properties of each input, we are going to exclude the **validations, typeValue and value**, because they are values that our input does not need directly.

> One thing to improve about this function, is that you can create a separate component, and create a dictionary with the components and the type of input.
> In this case I do not do it, so as not to extend more.
>

```tsx
const createInputs = () =>
    inputs.map(({ validations, typeValue, value, ...inputProps }) => {
        
        switch (inputProps.type) {
            case 'select':
                return <CustomSelect {...inputProps} key={inputProps.name} />
            case 'checkbox':
                return <CustomCheckbox {...inputProps} key={inputProps.name} />
            case 'radio':
                return <CustomRadio {...inputProps} key={inputProps.name} />
            default:
                return <CustomInput {...inputProps} key={inputProps.name} />
        }
    })

```

Finally, we execute the **createInputs** function inside the section tag. And immediately we are going to create the custom inputs.

```tsx
// imports 

// interface
export const Form = ({ ...props }: Props) => {
	// props

	const formMethods = useForm({
		resolver: yupResolver(validationSchema),
		defaultValues: { ...(initialValues as any) }
	})

	const createInputs = () =>
		inputs.map(({ validations, typeValue, value, ...inputProps }) => {
			switch (inputProps.type) {
				case 'select':
					return <CustomSelect {...inputProps} key={inputProps.name} />
				case 'checkbox':
					return <CustomCheckbox {...inputProps} key={inputProps.name} />
				case 'radio':
					return <CustomRadio {...inputProps} key={inputProps.name} />
				default:
					return <CustomInput {...inputProps} key={inputProps.name} />
			}
		})

	return (
		<FormProvider {...formMethods}>
			<form
				onSubmit={formMethods.handleSubmit(onSubmit)}
				className='bg-secondary rounded-md p-10 pt-5 shadow-2xl shadow-primary/30 flex flex-col gap-2 border border-primary w-full min-h-[390px]'
			>
				<section className='flex-1 flex flex-col gap-3'>
                    { createInputs() }
                </section>

			</form>
		</FormProvider>
	)
}
```

## 💊 Creating the components of each input. <a id="10"></a>

First, we are going to create an error message, which is going to be displayed every time the input validation fails.

Inside **src/components** we create **ErrorMessage.tsx**.
```tsx
interface Props { error?: string }

export const ErrorMessage = ({ error }: Props) => {
	if (!error) return null

	return (
		<div className='w-full grid place-content-end'>
			<p className='text-red-400 text-sm'>{error}</p>
		</div>
	)
}
```
Now, we are going to create a new folder **src/components/inputs** and inside we will create 4 files.

> These four components that we are going to create receive props that are of type **CustomInputProps**. You can place it in the src/types/index.ts file.
>```tsx
>export type CustomInputProps = Omit<InputProps, 'validations' | 'typeValue' | 'value'>
>```
>

And also as each input that we will create will be inside a **FormProvider**, we can use another custom hook of react-hook-form, which is **useFormContext**, this hook will help us to connect the state of the form with the input.


1. **CustomGenericInput.tsx**

From **useFormContext**, we obtain the register property, and the errors property inside the formState.

```tsx
const {
		register,
		formState: { errors }
	} = useFormContext()
```

We create the error, computing the error object with the prop name that the component receives and we obtain the message.

```tsx
const error = errors[name]?.message as string | undefined
```

At the moment of constructing the input, we need to spread the properties of the **register** function, which we have to pass the prop **name** so that react-hook-form identifies what errors and validations this input should have.
Then, we spread the other properties in case it has more (such as the **placeholder**).

```tsx
<input
    className='py-1 px-2 rounded w-full text-black'
    {...register(name)}
    {...props}
    id={id}
/>
```

This is how this component will look like in the end.

```tsx
import { useFormContext } from 'react-hook-form'
import { ErrorMessage } from '../../components'
import { CustomInputProps } from '../../types'

export const CustomInput = ({ name, label, ...props }: CustomInputProps) => {
	const {
		register,
		formState: { errors }
	} = useFormContext()

	const error = errors[name]?.message as string | undefined

	const id = `${name}-${props.type}-${label}`

	return (
		<div className='w-full flex gap-1 flex-col'>
			{label && (
				<label className='text-white text-sm' htmlFor={id}>
					{label}
				</label>
			)}

			<input
				className='py-1 px-2 rounded w-full text-black'
				{...register(name)}
				{...props}
				id={id}
			/>

			<ErrorMessage error={error} />
		</div>
	)
}
```

2. **CustomCheckbox.tsx**

```tsx
import { useFormContext } from 'react-hook-form'
import { ErrorMessage } from '../../components'
import { CustomInputProps } from '../../types'

export const CustomCheckbox = ({ name, label, ...props }: CustomInputProps) => {
	const {
		register,
		formState: { errors }
	} = useFormContext()

	const error = errors[name]?.message as string | undefined

	return (
		<div>
			<label className='flex gap-2 items-center cursor-pointer w-fit'>
				<input {...props} {...register(name)} />
				{label}
			</label>

			<ErrorMessage error={error} />
		</div>
	)
}
```

3. **CustomSelect.tsx**

This input is almost the same as all the others, only here we have the prop options where the values of the select that can be selected will come.

```tsx
import { useFormContext } from 'react-hook-form'
import { ErrorMessage } from '../../components'
import { CustomInputProps } from '../../types'

export const CustomSelect = ({ name, label, options, ...props }: CustomInputProps) => {
	const {
		register,
		formState: { errors }
	} = useFormContext()

	const error = errors[name]?.message as string | undefined
	const id = `${name}-${props.type}-${label}`

	return (
		<div className='flex flex-col gap-2'>
			<div className='flex items-center gap-4'>
				<label htmlFor={id}>{label}</label>

				<select {...register(name)} {...props} id={id} className='p-2 rounded flex-1 text-black'>
					
                    <option value=''>--- Select option ---</option>

					{options &&
						options.map(({ desc, value }) => (
							<option key={value} value={value}>
								{desc}
							</option>
					))}

				</select>

			</div>
			<ErrorMessage error={error} />
		</div>
	)
}
```

4. **CustomRadioGroup**

Very similar to CustomSelect.tsx. Only here we render an input of type radio.

```tsx
import { useFormContext } from 'react-hook-form'
import { ErrorMessage } from '../../components'
import { CustomInputProps } from '../../types'

export const CustomRadio = ({ name, label, options, ...props }: CustomInputProps) => {
	const {
		register,
		formState: { errors }
	} = useFormContext()

	const error = errors[name]?.message as string | undefined

	return (
		<div className='flex flex-col'>
			<div className='flex items-center gap-4'>
				<label>{label}</label>

				<section className='flex justify-between flex-1'>
					{options &&
						options.map(({ desc, value }) => (

							<label
								key={value}
								className='flex items-center gap-1 cursor-pointer hover:underline rounded p-1'
							>
								<input {...register(name)} {...props} value={value} type='radio' />
								{desc}
							</label>

						))}
				</section>
			</div>
			<ErrorMessage error={error} />
		</div>
	)
}
```

## 💊 Using our Form component. <a id="11"></a>

Now we go to the **src/App.tsx** file.

To use the Form component.

We have to execute the **getInputs** function and get the validations, initial values and inputs.  We will do it outside the component. We also create an interface so that the initial values behave like that interface.

```tsx
interface SignUpFormType {
	username: string
	password: string
	repeat_password: string
}

const signUpForm = getInputs<SignUpFormType>('register')
```

Then we import the Form component, we spread the properties returned by **getInput**. And we also pass it the other props.

```tsx
import { Layout, Form } from './components'
import { getInputs } from './lib'

interface SignUpFormType {
	username: string
	password: string
	repeat_password: string
}


const signUpForm = getInputs<SignUpFormType>('register')

const App = () => {

	const onSubmitSignUp = (data: unknown) => console.log({ singUp: data })

	return (
		<Layout>
			<Form
				{...signUpForm}
				onSubmit={onSubmitSignUp}
				titleForm='Sign Up!'
				labelButtonSubmit='Create account'
			/>
		</Layout>
	)
}
export default App
```


In case you want to overwrite the initial values, you just create a new constant by spreading the initial values and then overwriting what you need. Then pass a new value to the **initialValues** prop.


```tsx

const App = () => {
	const onSubmitSignUp = (data: unknown) => console.log({ singUp: data })
	
	const initialValuesSignUp: SignUpFormType = {
		...signUpForm.initialValues,
		username: '@franklin361'
	}

	return (
		<Layout>
			<Form
				{...signUpForm}
				
                initialValues={initialValuesSignUp}

				onSubmit={onSubmitSignUp}
				titleForm='Sign Up!'
				labelButtonSubmit='Create account'
			/>
		</Layout>
	)
}
export default App
```

And you can also include several forms dynamically.

```tsx
import { Layout, Form } from './components'
import { getInputs } from './lib'

interface SignUpFormType {
	username: string
	password: string
	repeat_password: string
}

interface AnotherFormType {}

const signUpForm = getInputs<SignUpFormType>('register')
const anotherForm = getInputs<AnotherFormType>('another')

const App = () => {
	const onSubmitSignUp = (data: unknown) => console.log({ singUp: data })

	const onSubmitAnotherForm = (data: unknown) => console.log({ another: data })

	const initialValuesSignUp: SignUpFormType = {
		...signUpForm.initialValues,
		username: '@franklin361'
	}

	return (
		<Layout>
			<Form
				{...signUpForm}
				initialValues={initialValuesSignUp}
				titleForm='Sign Up!'
				onSubmit={onSubmitSignUp}
				labelButtonSubmit='Create account'
			/>

			<Form
				{...anotherForm}
				titleForm='Another form!'
				onSubmit={onSubmitAnotherForm}
				labelButtonSubmit='Send info'
			/>
		</Layout>
	)
}
export default App
```

![cover](https://user-images.githubusercontent.com/65827956/216846161-b7acb18c-d738-4200-bf3a-5cb70844b465.png)


## 💊 Conclusion. <a id="12"></a>

React Hook Form is one of my favorite libraries, because it has certain advantages over other popular libraries such as Formik; for example the bundle size is smaller, it has fewer dependencies, produces fewer re-renders, etc. 😉.

But still both are very used libraries.

I hope you liked this post and I also hope I helped you to understand how to make dynamic forms using React Hook Form 🙌.

If you know any other different or better way to make this application feel free to comment it.

> **I invite you to review my portfolio in case you are interested in contacting me for a project.! [Franklin Martinez Lucas](https://franklin-dev.netlify.app/)**

> 🔵 Don't forget to follow me also on twitter: [**@Frankomtz361**](https://twitter.com/Frankomtz361)

### 💊 Demo. <a id="13"></a>

[https://dynamic-form-rhf.netlify.app/](https://dynamic-form-rhf.netlify.app/)

### 💊 Source code. <a id="14"></a>

[https://github.com/Franklin361/dynamic-form-rhf/](https://github.com/Franklin361/dynamic-form-rhf/)