Cookie Consent by Free Privacy Policy Generator 📌 Formularios dinámicos con React Hook Form. 📝

🏠 Team IT Security News

TSecurity.de ist eine Online-Plattform, die sich auf die Bereitstellung von Informationen,alle 15 Minuten neuste Nachrichten, Bildungsressourcen und Dienstleistungen rund um das Thema IT-Sicherheit spezialisiert hat.
Ob es sich um aktuelle Nachrichten, Fachartikel, Blogbeiträge, Webinare, Tutorials, oder Tipps & Tricks handelt, TSecurity.de bietet seinen Nutzern einen umfassenden Überblick über die wichtigsten Aspekte der IT-Sicherheit in einer sich ständig verändernden digitalen Welt.

16.12.2023 - TIP: Wer den Cookie Consent Banner akzeptiert, kann z.B. von Englisch nach Deutsch übersetzen, erst Englisch auswählen dann wieder Deutsch!

Google Android Playstore Download Button für Team IT Security



📚 Formularios dinámicos con React Hook Form. 📝


💡 Newskategorie: Programmierung
🔗 Quelle: dev.to

En esta ocasión, volveremos a crear formularios dinámicos pero ahora con ayuda de la librería de react-hook-form.

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. 😉

 

Tabla de contenido.

📌 Tecnologías a utilizar.

📌 Creando el proyecto.

📌 Primeros pasos.

📌 Creando el objeto de formulario.

📌 Creando el tipado para los inputs.

📌 Ahora si, creamos el objeto de formulario con ayuda del tipado.

📌 Creando el esquema de validaciones para nuestro formulario.

📌 Función para generar los inputs.

📌 Creando el componente de formulario.

📌 Creando los componentes de cada input.

📌 Usando nuestro componente Form.

📌 Conclusión.

📌 Demostración.

📌 Código fuente.

 

💊 Tecnologías a utilizar.

  • 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.

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

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.

cd dynamic-forms-rhf

Luego instalamos las dependencias.

npm install

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

code .

💊 Primeros pasos.

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

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.

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.

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

💊 Creando el tipado para los inputs.

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

export type FormSection = 'register' | 'another'

💊 Ahora si, creamos el objeto de formulario con ayuda del tipado.

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:

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: "[email protected]",
            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.

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:

  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.

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

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.

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

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.

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.

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.

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.


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.

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.
validationSchema: Yup.object({ ...validationsFields }),
  • Los valores iniciales, y haremos que se comporten como genérico para poder usarlos después
initialValues: initialValues as T,

- Los campos que queremos mostrar en nuestro formulario.

inputs: forms[section]

Asi se vería al final nuestra función


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.

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.

interface Props {
    onSubmit: (data: unknown) => void
    labelButtonSubmit?: string
    titleForm?: string

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

La propiedad validationSchema es de tipo SchemaForm.

// 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.

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.

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.

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.

// 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.

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

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

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.

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.

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

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

Asi quedaría al final este componente.

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>
    )
}
  1. CustomCheckbox.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>
    )
}
  1. 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.

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>
    )
}
  1. CustomRadioGroup

Muy parecido al CustomSelect.tsx. Solo que aquí renderizamos un input de tipo radio.

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.

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.

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.

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.


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.

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

💊 Conclusión.

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

🔵 No olvides seguirme también en twitter: @Frankomtz361

💊 Demostración simple.

https://dynamic-form-rhf.netlify.app/

💊 Código fuente.

https://github.com/Franklin361/dynamic-form-rhf/

...



📌 Formularios dinámicos con React Hook Form. 📝


📈 120.64 Punkte

📌 Building a CRUD App with Next.js, React Query, React Hook Form, and Yup


📈 43.99 Punkte

📌 Creating Dynamic Forms with React, Typescript, React Hook Form and Zod


📈 43.99 Punkte

📌 Simple React-Hook-Form v7 Tutorial with Typescript


📈 34.8 Punkte

📌 Dynamic forms with React Hook Form. 📝


📈 34.8 Punkte

📌 How to Validate Forms with Zod and React-Hook-Form


📈 34.8 Punkte

📌 Fix the "React Hook is Called Conditionally" Error in React


📈 34.47 Punkte

📌 Demystifying React Memoization: Understanding React.memo() and the useMemo hook


📈 34.47 Punkte

📌 Difference between Action Hook and Filter Hook in WordPress


📈 32.15 Punkte

📌 Cgiscript.net csMailto csMailto.cgi form-to/form-from/form-results privilege escalation


📈 28.58 Punkte

📌 Top 10 React Form Libraries for Efficient Form Creation


📈 28.25 Punkte

📌 This Week In React #127: Nextra, React-Query, React Documentary, Storybook, Remix, Tamagui, Solito, TC39, Rome...


📈 27.59 Punkte

📌 This Week In React #139: React.dev, Remix, Server Components, Error Boundary, Wakuwork, React-Native, Bottom Sheet...


📈 27.59 Punkte

📌 This Week In React #146: Concurrency, Server Components, Next.js, React-Query, Remix, Expo Router, Skia, React-Native...


📈 27.59 Punkte

📌 Writing a Custom React useDebounce Hook with Typescript


📈 25.27 Punkte

📌 Writing a Custom useWindowSize React Hook


📈 25.27 Punkte

📌 Using Local Storage in React with Your Own Custom useLocalStorage Hook


📈 25.27 Punkte

📌 Writing Your Own useFetch Hook in React


📈 25.27 Punkte

📌 A useLocalStorage React Hook for JavaScript and Typescript


📈 25.27 Punkte

📌 How does the useMemo hook work in React?


📈 25.27 Punkte

📌 Better React Performance – When to Use the useCallback vs useMemo Hook


📈 25.27 Punkte

📌 Converting Your React Hook To TypeScript


📈 25.27 Punkte

📌 Understanding the useReducer hook in react


📈 25.27 Punkte

📌 Using the useRef hook to change an element’s style in React


📈 25.27 Punkte

📌 In-Depth Guide to Using useMemo() Hook in React


📈 25.27 Punkte

📌 Criei um hook personalizado para buscar dados da API do Rick and Morty com React Query


📈 25.27 Punkte

📌 Usage of useMemo hook in React


📈 25.27 Punkte

📌 Creating a True/False Toggle in React with useState Hook for Beginners


📈 25.27 Punkte

📌 React Router: A Beginners guide to useParams hook.


📈 25.27 Punkte

📌 React Performance Booster - Introduction to the useMemo hook


📈 25.27 Punkte

📌 How to Build a Custom React Hook to Listen for Keyboard Events


📈 25.27 Punkte

📌 A Guide to Debouncing in JavaScript and React | Create a Custom Hook


📈 25.27 Punkte











matomo