Formularios dinámicos con React Hook Form. 📝
Tags: javascript, react, tutorial, spanish
Published: 2023-03-10 13:46:53

En esta ocasión, volveremos a crear formularios dinámicos pero ahora con ayuda de la librería de [**react-hook-form**](https://react-hook-form.com/).

> Nota 📣: **Necesitas tener conocimiento en Typescript para seguir este tutorial, así como de React JS**.
>

Tal vez te interese este articulo, donde también hacemos lo mismo que en este post, pero usando la librería de [**Formik**](https://formik.org/). 😉

{% embed https://dev.to/franklin030601/formularios-dinamicos-con-formik-y-react-js-4hne %}


&nbsp;

## Tabla de contenido.

> 📌 [Tecnologías a utilizar.](#1)  
> 📌 [Creando el proyecto.](#2)  
> 📌 [Primeros pasos.](#3)  
> 📌 [Creando el objeto de formulario.](#4)  
>> 📌 [Creando el tipado para los inputs.](#5)  
>> 📌 [Ahora si, creamos el objeto de formulario con ayuda del tipado.](#6)  
>
> 📌 [Creando el esquema de validaciones para nuestro formulario.](#7)  
> 📌 [Función para generar los inputs.](#8)  
> 📌 [Creando el componente de formulario.](#9)  
> 📌 [Creando los componentes de cada input.](#10)  
> 📌 [Usando nuestro componente Form.](#11)  
> 📌 [Conclusión.](#12)
>
> > 📌 [Demostración.](#13)  
> > 📌 [Código fuente.](#14)

&nbsp;

## 💊 Tecnologías a utilizar. <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** (no se muestra el proceso de instalación ni de configuración)

## 💊 Creando el proyecto. <a id="2"></a>

Al proyecto le colocaremos el nombre de: **`dynamic-forms-rhf`** (opcional, tu le puedes poner el nombre que gustes).

```bash
npm create vite@latest
```

Creamos el proyecto con Vite JS y seleccionamos React con TypeScript.  
Luego ejecutamos el siguiente comando para navegar al directorio que se acaba de crear.

```bash
cd dynamic-forms-rhf
```

Luego instalamos las dependencias.

```bash
npm install
```

Después abrimos el proyecto en un editor de código (en mi caso VS code).

```bash
code .
```

## 💊 Primeros pasos. <a id="3"></a>

Dentro del archivo **src/App.tsx** borramos todo y creamos un componente que muestre un `hola mundo`

```tsx
const App = () => {
  return <div>Hello world</div>;
};
export default App;
```

> 🚨 Nota: Cada vez que creamos una nueva carpeta, también crearemos un archivo **index.ts** para agrupar y exportar todas las funciones y componentes de otros archivos que están dentro de la misma carpeta, y que dichas funciones puedan ser importadas a traves de una sola referencia a esto se le conoce como **archivo barril**.

Vamos a crear un layout, creamos una carpeta **src/components** y dentro creamos un archivo **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>
		</>
	)
}
```

Ahora, dentro de archivo **src/App.tsx**, agregamos el layout.

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

const App = () => {

	return (
		<Layout>
			<span>Form</span>
		</Layout>
	)
}
export default App
```

Luego vamos a instalar los paquetes necesarios.
- **react-hook-form**, para manejar los formularios de una manera mas fácil.
- **yup**, para manejar validaciones en los formularios.
- **@hookform/resolvers**, para integrar yup con react-hook form.

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

> Anteriormente ya había realizado este mismo ejercicio de formularios dinámicos pero usando la librería de Formik, y la verdad es muy similar lo que vamos a realizar, lo único que cambiar son los componentes como el formulario e inputs.
>

## 💊 Creando el objeto de formulario. <a id="4"></a>

### 💊 Creando el tipado para los inputs. <a id="5"></a>

Antes, vamos a crear el tipado. Creamos una nueva carpeta **src/types** y creamos el archivo index.ts.

Ahora primero creamos la interfaz para los inputs, el cual puede incluso tener más propiedades, pero estas son suficientes para hacer este ejemplo.

Lo mas destacado son las ultimas tres propiedades de la interfaz **InputProps**:
- **typeValue**: necesaria ya que necesitamos indicarle a Yup que tipo de valor acepta el input.
- **validations**: validaciones que se le establecerán a Yup en base al input; solo coloque validaciones básicas, aunque puedes integrar más si buscas en la documentación de Yup. 
	-  La validación que tal vez se mas se te complique puede ser **oneOf**, si no haz usado Yup. Esta validación, necesita de una referencia o sea el name de otro input para validar si ambos input contienen el mismo contenido. Un ejemplo de donde usar esta validación es en un input donde creas un password y otro donde tienes que repetir password y ambos valores tienen que coincidir. 
- **options**: esta propiedad es necesario solo si el input es un select o un grupo de inputs de tipo radio

```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
}
```

También de una vez creamos este tipo para los tipos de formularios que vamos a desarrollar.
**En este caso solo vamos a crear dos formularios.**

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

### 💊 Ahora si, creamos el objeto de formulario con ayuda del tipado. <a id="6"></a>

Gracias a Typescript podemos crear nuestros formularios en este objeto.
Creamos una nueva carpeta **src/lib** y dentro creamos el archivo **form.ts** y agregamos lo siguiente:

```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!"
                }
            ]
        },
    ]
}
```



## 💊 Creando el esquema de validaciones para nuestro formulario. <a id="7"></a>

Vamos a crear un nuevo archivo en src/lib y lo nombramos **getInputs.ts**
Creamos una nueva función para generar las validaciones a cada input.
Esta función recibe los campos, y cada campo es de tipo InputProps. También vamos a crear 2 tipos solamente para que Typescript no nos moleste mas adelante.

> Nota que creamos los tipos YupBoolean y YupString. Si quieres puedes agregar otros tipos ya sea para manejar algún otro tipo de dato como el numérico o el arreglo. Por ejemplo:
>```tsx
>   type YupNumber = Yup.NumberSchema<boolean | undefined, AnyObject, number | undefined>
>```
> Yo no lo agrego, porque en mis interfaces no manejo alguna validación de tipo numero o arreglo.


```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) => {}
```

Primero creamos una variable que se inicializara con el tipo de dato que manejara nuestro input. El tipo de dato lo obtenemos de la propiedad **typeValue**, en caso de que sea *undefined* por defecto el tipo de dat sera *string*, y entonces ejecutamos la función

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

Luego vamos a recorrer las validaciones del campo, ya que es un arreglo. 

Dentro del ciclo, usaremos un switch case, evaluando que tipo de regla es la que tiene dicho campo. 

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

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

    for (const rule of field.validations) {

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

En cada caso del switch vamos a sobrescribir la variable schema. De la siguiente manera:

Si tiene una validación 'isTrue' significa que el input maneja valores booleanos, por lo cual queremos que nuestro schema se comporte como un YupBoolean, ya que si no Typescript se estaría quejando. Luego ejecutamos la función que tenga que ver con cada caso. 

Por ejemplo, en el caso de 'isTrue', ejecutamos la función que se llama exactamente igual, y dentro le pasamos el mensaje

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

En el caso de que la validación sea oneOf, necesitamos enviarle, como primer parámetro un arreglo y como segundo parámetro un mensaje.

En el caso del arreglo, debe ser el valor con el que quieres que coincida, pero en este caso queremos que coincida con el valor de otro campo, por eso usamos **Yup.ref** el cual necesita un string que hace referencia al atributo **name** de un input.
Para que asi cuando se haga la validación, se verifique si ambos campos contienen el mismo valor.

```tsx
case 'oneOf'    : schema = (schema as YupString)
                                            .oneOf(
                                                    [ Yup.ref(rule.ref as string) ], 
                                                    rule.message
                                                  ); 
break;
```

Asi quedaría nuestra primera función. Al final retornamos la variable **schema**.
Nota que al inicio de la función, colocamos una condición donde si el campo no tiene validaciones entonces retornar null y evitar ejecutar el ciclo.

```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
}
```

## 💊 Función para generar los inputs. <a id="8"></a>

Primero vamos a crear un función y la nombramos getInputs, la cual es de tipo genérico y recibe como parámetro la **section** (o sea que formulario quieres obtener sus campos, en este caso puede ser el formulario de signUp o el otro).

Vamos a crear dos variables que las inicializaremos como objetos vacíos y que al final deberán contener nuevas propiedades.

```tsx

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

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

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

};
```

Dentro de la función haremos un ciclo for of. En el caul vamos a recorrer los campos de un formulario en especifico.

1. Dentro del ciclo, vamos a computar los valores en la variable de initialValues, y para computar los valores usamos la propiedad name del campo.

2. Verificamos si existen validaciones para el campo.
    - Si no hay validaciones, entonces seguir con el siguiente campo.
    - Si hat validaciones, ejecutamos la función que creamos anteriormente **generateValidations** mandando el campo como argumento.

3. Luego a la variable validationsFields, también le computamos los valores usando la propiedad name del campo, y le asignamos el schema de validación que se ha generado.

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

    initialValues[field.name] = field.value;

    if (!field.validations) continue;

    const schema = generateValidations(field)

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

Una vez terminado el ciclo, debemos retornar 3 propiedades.
- El esquema de validación dentro de un **Yup.object**, esparciendo las propiedades de **validationsFields**.
```tsx
validationSchema: Yup.object({ ...validationsFields }),
```
- Los valores iniciales, y haremos que se comporten como genérico para poder usarlos después
```tsx
initialValues: initialValues as T,
```
- Los campos que queremos mostrar en nuestro formulario. 
-
```tsx
inputs: forms[section]
```
Asi se vería al final nuestra función

```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],
    };

};

```

## 💊 Creando el componente de formulario. <a id="9"></a>

Primero vamos a preparar la interfaz para las props que va a recibir nuestro componente Form.

- **onSubmit**, función que ejecuta el formulario.
- **labelButtonSubmit**, texto que mostrara el botón.
- **titleForm**, texto que mostrara el formulario.

Los ultimas 3 propiedades son lo que regresa la función que hicimos para generar los inputs y sus validaciones.
```tsx
interface Props {
	onSubmit: (data: unknown) => void
	labelButtonSubmit?: string
	titleForm?: string

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

La propiedad validationSchema es de tipo **SchemaForm**.
```tsx
// src/types/index.ts
export type SchemaForm = OptionalObjectSchema<{
    [x: string]: any;
}, AnyObject, TypeOfShape<{
    [x: string]: any;
}>>
```

Ahora creamos el componente, y dentro desestructuramos las props que recibe el componente.

Luego usamos el hook de useForm, el cual vamos a establecer un objeto como argumento, accedemos a la propiedad:
 - **resolver**, para establecer el esquema de validación, para ello usamos la función **yupResolver** y le pasamos como argumento el **validationSchema** que viene por props.
 - **defaultValues**,  para establecer los valores por defecto y le asignaremos la props de **initialValues**.

Toma en cuenta que no desestructuramos nada del hook **useForm**.

```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 (
		<></>
	)
}
```
Después, vamos a usar un componente que nos ofrece react-hook-form, que es el **FormProvider** y le vamos  a esparcir los  **formMethods** del hook **useForm**.

El **FormProvider** nos ayudara a comunicar el estado del formulario con los componentes (inputs) que estén anidados dentro del FormProvider. Con el propósito de separar los componentes y no tener todo en un mismo archivo.

Dentro del FormProvider colocaremos un form y en el método onSubmit de la etiqueta form, vamos a ejecutar una propiedad del formMethods, que es el **handleSubmit**, y como argumento le pasamos el onSubmit que recibe el componente Form por props. 

Este handleSubmit, solo  se va a ejecutar si no hay errores en cada input, y cuando se ejecute nos devolverá los valores de cada 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>
	)
}
```

Ahora vamos a crear una función, para retornar los diferentes tipos de inputs.
Usamos la prop **inputs** que desestructuramos de las props que recibe el componente Form.


En base al tipo de input vamos a renderizar un uno u otro input.

Nota que estamos usando componentes que aun no hemos creado. También nota, que de las propiedades de cada input, vamos a excluir las **validations, typeValue y value**, porque son valores que no necesita nuestro input directamente.

> Una cosa a mejorar sobre esta función, es que puedes crear un componente aparte, y crear un diccionario con los componentes y el tipo de input.
> En este caso no lo hago, para no extenderme  más.
>

```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} />
        }
    })

```

Finalmente, ejecutamos la función **createInputs** dentro de la etiqueta section. E inmediatamente vamos a crear los 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>
	)
}
```

## 💊 Creando los componentes de cada input. <a id="10"></a>

Primero, vamos a crear un mensaje de error, que se va a mostrar cada que la validación del input falle.

Dentro de **src/components** creamos **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>
	)
}
```
Ahora, vamos a crear una nueva carpeta **src/components/inputs** y dentro crearemos 4 archivos.

> Estos cuatro componentes que vamos a crear reciben props que son de tipo **CustomInputProps**. Puedes colocarlo en el archivo src/types/index.ts 
>```tsx
>export type CustomInputProps = Omit<InputProps, 'validations' | 'typeValue' | 'value'>
>```
>

Y ademas como cada input que crearemos estará dentro de un **FormProvider**, podemos utilizar otro custom hook de react-hook-form, el cual es **useFormContext**, este hook nos ayudara a conectar el estado del formulario con el input.


1. **CustomGenericInput.tsx**

De useFormContext, obtenemos el la propiedad register, y la propiedad errors dentro del formState.

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

Creamos el error, computando el objeto de errors con la prop name que recibe el componente y obtenemos el mensaje.
```tsx
const error = errors[name]?.message as string | undefined
```

Al momento de construir el input, necesitamos esparcir las propiedades de la función **register**, el cual tenemos que pasar le la prop **name** para que react-hook-form identifique que errores y validaciones debe tener este input.
Luego le esparcimos las demás propiedades en caso de que tenga más (como por ejemplo el **placeholder**).
```tsx
<input
    className='py-1 px-2 rounded w-full text-black'
    {...register(name)}
    {...props}
    id={id}
/>
```

Asi quedaría al final este componente.

```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**

Este input es casi igual que todos los demás, solo que aquí tenemos la prop options donde vendrán los valores del select que se pueden seleccionar.

```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**

Muy parecido al CustomSelect.tsx. Solo que aquí renderizamos un input de tipo 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>
	)
}
```

## 💊 Usando nuestro componente Form. <a id="11"></a>

Ahora vamos al archivo **src/App.tsx**

Para usar el componente Form.

Tenemos que ejecutar la función **getInputs** y obtener las validaciones, valores iniciales e inputs.  Lo haremos fuera del componente. También creamos una interfaz para que los valores iniciales se comporten como dicha interfaz.

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

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

Luego importamos el componente Form, le esparcimos las propiedades que nos regresa **getInput**. Y también le pasamos las demás 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
```

En el caso de que quieras sobrescribir los valores iniciales, solamente te creas una nueva constante esparciendo los valores iniciales y luego sobrescribiendo lo que necesites. Para luego pasar le un nuevo valor a la prop de **initialValues**.


```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
```

Y asi mismo puedes incluir varios formularios de manera dinámica.

```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)


## 💊 Conclusión. <a id="12"></a>

React Hook Form es una de mis librearas favoritas, ya que tiene ciertas ventajas sobre otras librerías populares como por ejemplo Formik; como por ejemplo el bundle size es mas pequeño, tiene menos dependencias, produce menos re-renders, etc. 😉

Pero de igual manera ambas son librerías muy usadas.

Espero que te haya gustado esta publicación y que también espero haberte ayudado a entender como realizar formularios dinámicos usando React Hook Form. 🙌

Si conoces alguna otra forma distinta o mejor de realizar esta aplicación con gusto puedes comentarla.

> **Te invito a que revises mi portafolio en caso de que estés interesado en contactarme para algún proyecto! [Franklin Martinez Lucas](https://franklin-dev.netlify.app/)**

> 🔵 No olvides seguirme también en twitter: [**@Frankomtz361**](https://twitter.com/Frankomtz361)

### 💊 Demostración simple. <a id="13"></a>

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

### 💊 Código fuente. <a id="14"></a>

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