Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
October 28, 2022 10:00 am GMT

Formularios dinmicos con Formik y React JS.

En esta ocasin te mostrare como crear formularios dinmicos con Formik y Yup, usando React JS con TypeScript.

Cualquier tipo de feedback es bienvenido, gracias y espero disfrutes el articulo.

Nota: Es necesario que cuentes con conocimientos bsicos en React JS y hooks y TypeScript.

Tabla de contenido

Tecnologas a utilizar.

Creando el proyecto.

Primeros pasos.

Diseando el formulario.

Implementando Formik en nuestro formulario.

Diseando nuestro formulario dinmico.

Creando el componente Input.

Creando el componente Checkbox.

Creando el componente RadioGroup.

Creando el componente Select.

Creando el objeto del formulario.

Generando las reglas de validacin mediante funciones.

Inicializado los valores del formulario.

Usando la funcin getInputs.

Conclusin.

Cdigo fuente.

Tecnologas a utilizar.

  • React JS (version 18)
  • Vite JS
  • TypeScript
  • Formik
  • CSS vanilla (Los estilos los encuentras en el repositorio al final de este post)

Creando el proyecto.

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

npm init 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 formik-dynamic

Luego instalamos las dependencias.

npm install

Despus abrimos el proyecto en un editor de cdigo (en mi caso VS code).

code .

Primeros pasos.

Vamos al archivo src/App.tsx y borramos todo el contenido para crear un nuevo componente. Que por el momento solo renderizar un "hola mundo"

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

Lo que sigue es crear un Layout, esto solo es con propsito de esttica para la app, no es obligatorio.

Nota: Cada vez que creamos una nueva carpeta, tambin crearemos un archivo index.ts para agrupar y exportar todas las funciones y componentes de otros archivos que estn 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.

Creamos la carpeta src/components y dentro creamos el archivo Layout.tsx para agregar:

interface ILayout {    children: JSX.Element | JSX.Element[]    title: string}export const Layout = ({ children, title }: ILayout) => {    return (        <div className="container">            <h2 className="title">{title}</h2>            {children}        </div>    )}

Despus vamos a crear la carpeta src/pages. El propsito es simular paginas diferentes ya que vamos a explicar una forma bsica en la que se suele usar formik, despus otra pagina sera para explicar los formularios dinmicos

Crearemos un archivo FormikBasic.tsx para agregar por el momento lo siguiente:

import { Layout } from "../components"export const FormikBasic = () => {    return (        <Layout title="Formik Basic">            <div>Hello world</div>        </Layout>    )}

Luego lo importamos en el archivo src/App.tsx

import { FormikBasic } from "./pages"const App = () => {  return (    <>      <FormikBasic />    </>  )}export default App

Diseando el formulario.

Dentro de src/components/FormikBasic.tsx vamos a crear un formulario bsico, que use algunos de los controles mas comunes en un formulario.

import { Layout } from "../components"export const FormikBasic = () => {    return (        <Layout title="Formik Basic">            <form>                <input type="text" placeholder="Full name"/>                <input type="email" placeholder="E-mail"/>                <input type="password" placeholder="Password"/>                <div>                    <label htmlFor="rol">Select an option:</label>                    <select id="rol" >                        <option value="">--- Select ---</option>                        <option value="admin">Admin</option>                        <option value="user">User</option>                        <option value="super">Super Admin</option>                    </select>                </div>                <div className='radio-group'>                    <b>Gender: </b>                    <label ><input type="radio"/> Man</label>                    <label ><input type="radio"/> Woman</label>                    <label ><input type="radio"/> Other</label>                </div>                <label>                    <input type="checkbox" {...getFieldProps('terms')} />                    Terms and Conditions                </label>                <button type="submit">Submit</button>            </form>        </Layout>    )}

Ya con estilos, debera verse asi :

basic form

Implementando Formik en nuestro formulario.

Procedemos a instalar Formik para administrar nuestro formulario y otro paquete muy util sera Yup para manejar las validaciones de nuestro formulario.

npm install formik yup

Normalmente para usar Formik y manejar nuestros formularios lo haramos mediante el hook useFormik

Al hook use Formik del pasaremos un objeto con 3 propiedades (tiene ms propiedades pero solo usaremos esas 3)

  • initialValues, es un objeto con los valores iniciales de nuestro formulario, que hace referencia a cada input o campo.
  • validationSchema, aqu bsicamente es donde usaremos Yup para establecer las validaciones a nuestros respectivos campos del formulario
  • onSubmit, es una funcin que recibe los valores del formulario y solo se ejecuta cuando se pasan todas las validaciones.
useFormik({    initialValues:,    validationSchema: ,    onSubmit:});

initialValues, va a tener un objeto definiendo cada campo del formulario y sern inicializados con un string vaci y el campo terms sera un valor boolean inicializado en false.

validationSchema, va a tener un Yup.object (No se olviden de importar Yup en la parte superior del archivo: import * as Yup from 'yup' ), que es una funcin que recibe un objeto definiendo las validaciones. (Nota que las llaves de cada propiedad deben coincidir con las propiedades de initialValues). Dentro de cada propiedad tendr sus reglas de validacin

onSubmit, en realidad no har nada mas que mostrar los valores en consola.

const { handleSubmit, errors, touched, getFieldProps } = useFormik({    initialValues: {        fullName: '',        email: '',        password: '',        rol: '',        gender: '',        terms: false    },    validationSchema: Yup.object({        fullName: Yup.string().min(3, 'Min. 3 characters').required('Required'),        email: Yup.string().email('It should be a valid email').required('Required'),        password: Yup.string().min(6, 'Min. 6 characters').required('Required'),        terms: Yup.boolean().isTrue('You must accept the terms!'),        rol: Yup.string().required('Required'),        gender: Yup.string().required('Required'),    }),    onSubmit: values => {        console.log(values)    }});

El hook retorna varias propiedades y funciones, pero solo necesitamos:

  • handleSubmit, es la funcin que le debes pasar a tu formulario para hacer el posteo del formulario. Esta funcin debe ser ejecutada en el evento onSubmit de la etiqueta form.

  • errors, es un objeto con los errores de cada campo, identificados con el nombre de las propiedades que colocaste en initialValues.

  • touched, indica si el input ha sido tocado, esto servir para ejecutar las validaciones de ese campo y mostrar el error despus de que en input haya sido tocado y no al inicio de la aplicacin cuando el usuario apenas ve el formulario. Esta prop, es un objeto el cual us props estn identificados con el nombre de las propiedades que colocaste en initialValues.

  • getFieldProps, es una funcin getter que nos trae diferentes atributos necesarios para que el input funcione (name, value, onChange, onBlur) que generalmente tambin las podemos obtener del useFormik, pero seria mas cdigo tener que colocar cada propiedad (es util cuando tenemos que hacer algo especifico con dicha propiedad. pero en este caso no). Recibe como parmetro un name, que debe con alguna propiedad de initialValues

const { handleSubmit, errors, touched, getFieldProps } = useFormik({    // ...props});

Ahora que tenemos nuestro hook listo, pasaremos a modificar nuestro JSX.

Primero en la etiqueta form colocamos el handleSubmit y el noValidate.

<form noValidate onSubmit={handleSubmit}>

Ahora en los input de tipo text, email y password, colocamos lo siguiente.

Esparcimos las propiedades que retorna getFieldProps (recibe como parmetro un name, que debe con alguna propiedad de initialValues).

El className hacemos la validacin donde si el input fue tocado y existe el error correspondiente con ese input, que se agregue la clase 'error_input'. (aunque en realidad nunca uso esa clase en los estilos).

<input    // ...attr    {...getFieldProps('password')}    className={`${(touched.password && errors.password) && 'error_input'}`}/>

Dicha condicin en el atributo className, puede ser usada para mostrar el mensaje de error:

{(touched.password && errors.password) && <span className="error">{errors.password}</span>}

En el caso del select y el input del tipo checkbox, solo le agregaremos el getFieldProps y esparcimos los valores que retorna dicha funcin.

<select id="rol" {...getFieldProps('rol')} >    // ...options</select><input type="checkbox" {...getFieldProps('terms')} />

En el caso del input de tipo radio.
Agregamos el getFieldProps y esparcimos los valores que retorna dicha funcin.

Le agregaremos un valor.

En la propiedad checked evaluaremos si la propiedad value de getFieldProps es igual a el valor de nuestro input, entonces debe estar activo ese input radio.

<input type="radio"    {...getFieldProps('gender')}    value='women'    checked={getFieldProps('gender').value === 'women'}/>

Todo nuestro componente lucira de la siguiente manera :

import * as Yup from 'yup';import { useFormik } from "formik";import { Layout } from "../components"export const FormikBasic = () => {    const { handleSubmit, errors, touched, getFieldProps } = useFormik({        initialValues: {            fullName: '',            email: '',            password: '',            rol: '',            gender: '',            terms: false        },        validationSchema: Yup.object({            fullName: Yup.string().min(3, 'Min. 3 characters').required('Required'),            email: Yup.string().email('It should be a valid email').required('Required'),            password: Yup.string().min(6, 'Min. 6 characters').required('Required'),            terms: Yup.boolean().isTrue('You must accept the terms!'),            rol: Yup.string().required('Required'),            gender: Yup.string().required('Required'),        }),        onSubmit: values => {            // TODO: some action        }    });    return (        <Layout title="Formik Basic">            <form noValidate onSubmit={handleSubmit}>                <input                    type="text"                    placeholder="Full name"                    {...getFieldProps('fullName')}                    className={`${(touched.fullName && errors.fullName) && 'error_input'}`}                />                {(touched.fullName && errors.fullName) && <span className="error">{errors.fullName}</span>}                <input                    type="email"                    placeholder="E-mail"                    {...getFieldProps('email')}                    className={`${(touched.email && errors.email) && 'error_input'}`}                />                {(touched.email && errors.email) && <span className="error">{errors.email}</span>}                <input                    type="password"                    placeholder="Password"                    {...getFieldProps('password')}                    className={`${(touched.password && errors.password) && 'error_input'}`}                />                {(touched.password && errors.password) && <span className="error">{errors.password}</span>}                <div>                    <label htmlFor="rol">Select an option:</label>                    <select id="rol" {...getFieldProps('rol')} >                        <option value="">--- Select ---</option>                        <option value="admin">Admin</option>                        <option value="user">User</option>                        <option value="super">Super Admin</option>                    </select>                </div>                <div className='radio-group'>                    <b>Gender: </b>                    <label >                        <input type="radio"                            {...getFieldProps('gender')}                            checked={getFieldProps('gender').value === 'man'}                            value='man'                        />                        Man                    </label>                    <label >                        <input type="radio"                            {...getFieldProps('gender')}                            checked={getFieldProps('gender').value === 'women'}                            value='women'                        />                        Woman                    </label>                    <label >                        <input type="radio"                            {...getFieldProps('gender')}                            checked={getFieldProps('gender').value === 'other'}                            value='other'                        />                        Other                    </label>                    {(touched.gender && errors.gender) && <span className="error">{errors.gender}</span>}                </div>                {(touched.rol && errors.rol) && <span className="error">{errors.rol}</span>}                <label>                    <input type="checkbox" {...getFieldProps('terms')} />                    Terms and Conditions                    {(touched.terms && errors.terms) && <span className="error">{errors.terms}</span>}                </label>                <button type="submit">Submit</button>            </form>        </Layout>    )}

Hasta aqu tenemos un uso bsico y funcional de un formulario con sus validaciones.

Diseando nuestro formulario dinmico.

Ahora vamos a crear una nueva pagina, en nuestro src/pages creamos FormikDynamic.tsx
Y por el momento agregamos:

import { Layout } from "../components"export const FormikDynamic = () => {    return (        <Layout title="Formik Dynamic">            <div>Hello world</div>        </Layout>    )}

Y este componente lo mostramos en src/App.tsx

import { FormikBasic, FormikDynamic } from "./pages"const App = () => {  return (    <>      <FormikDynamic />      {/* <FormikBasic /> */}    </>  )}export default App

Dentro de src/components/FormikDynamic.tsx vamos a agregar los componentes que Formik nos ofrece para administrar un formulario.

Importamos el componente Formik, el cual sus props son similares al hook useFormik y que a la vez usaremos las mismas 3 propiedades antes mencionadas. Despus estableceremos el initialValues y validationSchema.

import { Formik } from "formik"export const FormikDynamic = () => {    return (        <Layout title="Formik Dynamic">            <Formik                initialValues={{}}                validationSchema={{}}                onSubmit={ values => console.log(values) }            >             </Formik>        </Layout>    )}

El componente Formik usa el patron de "render props" y para ello dentro del componente recibe una funcin. Dicha funcin tambin devolvan ciertas propiedades como lo hace el hook useFormik, pero en este caso no las usaremos.

La funcin, va a renderizar otro componente de formik que es el Form ya que es parecido a la etiqueta form pero que ya tiene el handleSubmit.

import { Formik } from "formik"export const FormikDynamic = () => {    return (        <Layout title="Formik Dynamic">            <Formik                initialValues={{}}                validationSchema={{}}                onSubmit={ values => console.log(values) }            >             {                () => (                    <Form noValidate>                        <button className="btn btn_submit" type="submit">Submit</button>                    </Form>                )            }                     </Formik>        </Layout>    )}

Ahora necesitamos los inputs, pero en este caso los separaremos en componentes reutilizable.

Creando el componente Input.

Dentro de la carpeta src/components creamos el archivo CustomTextInput.tsx

En el archivo creamos un componente y definimos la interface de las props que le llegaran al componente.

El name es una de las partes mas importantes para poder identificar el input.

Al final colocamos [x: string]: any ya que si necesitas poner alguna otra prop, no tengas que establecerla en la interfaz evitando que crezca (es opcional, si quieres definir cada propiedad puedes hacerlo).

Por ejemplo, en la interface no tenemos definido la prop autoComplete pero cuando usemos el componente, no nos marcara error si colocamos como props autoComplete y su valor ('off' / 'on').

Agregamos un simple input sin atributos.

interface Props {    name: string;    type: string;    placeholder?: string;    [x: string]: any}export const CustomTextInput = (props: Props) => {    return ( <input /> )}

Ahora, usaremos el hook useField que nos proporciona formik. Este hook es la clave para conectar los inputs con Formik.

El hook useField recibe como argumento ya sea un objeto o un string pero siempre se le debe mandar el name del input. En este caso no hay problema con mandarle todo el objeto prop, o solo prop.name.

El hook retorna un arreglo con tres posiciones.

  • FieldProps, contiene todo los necesario para que funcione el input onChange, value, onBlur, etc.

  • FieldMetaProps, contiene valores computados sobre el campo que pueden ser utilizados para estilizar o cambiar el campo, como por ejemplo el touched y los errores.

  • FieldHelperProps, contiene funciones de ayuda que permiten cambiar imperativamente los valores de un campo. Por ejemplo setValue.

El que nos interesa es el valor de la primera posicin el FieldProps, y esparcimos tanto las props que le llegan al componente como el valor de field que nos da el hook.

import { useField } from "formik"interface Props {    name: string;    type: string;    placeholder?: string;    [x: string]: any}export const CustomTextInput = (props: Props) => {    const [field] = useField(props)    return (        <input {...field} {...props} />    )}

Podran tambin usar la segunda posicin (el FieldMetaProps) para mostrar los errores, que seria exactamente igual que en el Formik basic. Pero mejor usamos un componente que nos proporciona formik para mostrar los errores, el ErrorMessage.

ErrorMessage recibe obligatorio el name del input, por defecto no solo muestra el texto, sin etiqueta HTML por eso colocamos la propiedad component para decirle que renderize un span.

import { ErrorMessage, useField } from "formik"interface Props {    name: string;    type: string;    placeholder?: string;    [x: string]: any}export const CustomTextInput = (props: Props) => {    const [field] = useField(props)    return (        <>            <input {...field} {...props} />            <ErrorMessage name={props.name} component="span" className="error" />        </>    )}

Listo ya tenemos nuestro input.

Creando el componente Checkbox.

Hacer este componente input de tipo checkbox es bsicamente lo mismo que el input normal. Lo nico que cambia es la estructura del JSX y la interface

import { ErrorMessage, useField } from "formik"interface Props {    label: string;    name: string;    [x: string]: any}export const CustomCheckBox = (props: Props) => {    const [field] = useField(props)    return (        <label className="label_check">            <input type="checkbox" {...field} {...props} />            <span>{props.label}</span>            <ErrorMessage name={props.name} component="span" className="error" />        </label>    )}

Creando el componente RadioGroup.

Este componente consiste en un grupo de input tipo radio.

Es casi igual a los componentes anteriores, solo que aqu tenemos un arreglo de opciones que son los valores y descripciones del input.

Tenemos que recorrer dichas opciones y establecer su atributo value al input y tambin su atributo checked que tendr una condicin donde si el valor del field es igual al valor del input se activara el input.

Colocar el value es necesario porque este input no cambiara su valor siempre sera el mismo, lo que cambia es el valor del atributo checked.

Tambin colocamos el value para identificar al input, ya que los input radio, para que solo puedas seleccionar uno de un grupo, tienen que tener el mismo atributo name.

Asi que cuando el input hace un onChange, formik agarra el atributo value y los establece. luego se evala si el valor establecido es igual a uno de los valores del grupo de inputs entonces su atributo checked lo pondr en trae.

import { useField, ErrorMessage } from 'formik';type Opt = { value: string | number, desc: string }interface Props {    options: Opt[]    name: string    label: string    [x: string]: any}export const CustomRadioGroup = ({ label, options, ...props }: Props) => {    const [field] = useField(props)    return (        <div className='radio-group'>            <b>{label}</b>            {                options.map(opt => (                    <label key={opt.value}>                        <input                            {...field}                            {...props}                            type="radio"                            value={opt.value}                            checked={opt.value === field.value}                        />                        {opt.desc}                    </label>                ))            }            <ErrorMessage name={props.name} component="span" className="error" />        </div>    )}

Creando el componente Select.

Hacer este componente select es casi lo mismo que el radio group. Lo nico que cambia es la estructura del JSX, a la etiqueta select es al que se le esparce las propiedades de field y las que le lleguen a nuestro componente.

Dentro del select, recorremos las opciones y establecemos su valor y descripcin.

import { ErrorMessage, useField } from "formik"interface Props {    options: Opt[]    label: string;    name: string;    [x: string]: any}type Opt = { value: string | number, desc: string }export const CustomSelect = ({ label,options, ...props }: Props) => {    const [field] = useField(props)    return (        <>            <div>                <label htmlFor={props.name || props.id}> {label} </label>                <select {...field} {...props} >                    <option value="">--- Select ---</option>                    {                        options.map(({ desc, value }) => (                            <option                                value={value}                                key={value}                            >{desc}</option>                        ))                    }                </select>            </div>            <ErrorMessage name={props.name} component="span" className="error" />        </>    )}

Creando el objeto del formulario.

Aqu definiremos como queremos nuestro formulario, podra ser ya sea un archivo JSON ,pero en este caso lo hare con un objeto para colocarte los tipos desde el principio.

Creamos algunas interfaces.

Primero tenemos el InputProps donde tenemos las propiedades bsicas de un input y al final tenemos 4 propiedades que son:
-type, servir para saber que componente renderizar.
-typeValue, servir para saber que tipo de dato asignar a la instancia de Yup.
-options, Son los input radio o los options de un select
-validations, las reglas de validacin.

Luego tenemos la interface Opt que ya habamos usado antes en el CustomRadioGroup y CustomSelect, incluso pueden crear un archivo de interfaces para reutilizarlas.

Por ultimo tenemos la interface de Validation para establecer las reglas de validacin con Yup.
-type, es el tipo de validacin que queremos implementar al campo.
-value, el valor (opcional) que estableceremos a la validacin.
-message, el mensaje personalizado para mostrar.

export interface InputProps {    name: string    value: string | number | boolean    placeholder?: string    label?: string    type: 'text' | 'radio-group' | 'email' | 'password' | 'select' | 'checkbox'    typeValue?: 'string' | 'boolean'    options?: Opt[]    validations: Validation[]}export interface Opt {    value: string | number    desc: string}export interface Validation {    type: 'required' | 'isEmail' | 'minLength' | 'isTrue'    value?: string | number | boolean    message: string}

En base a las interfaces exportamos una constante que contiene un objeto con los formularios, que en este caso solo va a ser un formulario.

Aqu se acaba de crear exactamente el formulario bsico que hemos hecho con anterioridad.

export const forms: { [x: string]: InputProps[] } ={    login: [        {            type: "text",            name: "name",            placeholder: "Full Name",            value: "",            validations: [                {                    type: "minLength",                    value: 3,                    message: "Min. 3 characters",                },                {                    type: "required",                    message: "Full Name is required"                },            ],        },        {            type: "email",            name: "email",            placeholder: "E-mail",            value: "",            validations: [                {                    type: "required",                    message: "Email is required"                },                {                    type: "email",                    message: "Email no valid"                }            ],        },        {            type: "password",            name: "password",            placeholder: "Password",            value: "",            validations: [                {                    type: "required",                    message: "Password is required"                }            ],        },        {            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-group",            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!"                }            ]        },    ],}

Ahora toca crear una funcin para construir cada regla de validacin y los valores iniciales del formulario

Generando las reglas de validacin mediante funciones.

Vamos a src/utils y dentro creamos un archivo llamado getInputs.ts
Donde vamos a tener la primera funcin:

generateValidations, recibe solo un parmetro:

  • field, el primero es el campo con todos sus props, aunque solo necesitamos las validaciones y el tipo de valor que es el campo.
const generateValidations = (field: InputProps) => {}

Despus necesitamos crear un esquema vaci, el cual vamos a ir reasignando su valor.

Pero por el momento el schema puede ser un string o un boolean, ya que el nico valor boolean es el checkbox y los dems son string, pero aceptar otros valores primitivos.

const generateValidations = (field: InputProps) => {    let schema = Yup[field.typeValue ? field.typeValue : 'string']() // Yup.string() }

Luego vamos a recorrer cada validacin del campo. Las validaciones se encuentran en field.validations

import * as Yup from "yup";import { InputProps } from './forms';const generateValidations = (field: InputProps) => {    let schema = Yup[field.typeValue ? field.typeValue : 'string']()    for (const rule of field.validations) {}}

Entonces, vamos a evaluar el tipo de validacin que tiene el campo, y lo haremos con un switch.

Por default, al schema solo se le agrega la validacin de required y el mensaje.

import * as Yup from "yup";import { InputProps } from './forms';const generateValidations = (field: InputProps) => {    let schema = Yup[field.typeValue ? field.typeValue : 'string']()    for (const rule of field.validations) {        switch (rule.type) {            default: schema = schema.required(rule.message); break;        }    }}

Finalmente agregamos las dems validaciones. y retornamos el schema.

import * as Yup from "yup";import { AnyObject } from "yup/lib/types";import { InputProps } from './forms';type YupBoolean = Yup.BooleanSchema<boolean | undefined, AnyObject, boolean | undefined>type YupString = Yup.StringSchema<string | undefined, AnyObject, string | undefined>const generateValidations = (field: InputProps) => {    let schema = Yup[field.typeValue ? 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;            default: schema = schema.required(rule.message); break;        }    }    return schema}

Inicializado los valores del formulario.

Ahora necesitamos inicializar los valores de nuestro formulario.

Para ello vamos a crear un funcin que recibe un parmetro.

Notaran que en el archivo src/utils/forms.ts la constante forms exporta un objeto, bueno la idea es que la funcin que crearemos ahorita reciba una propiedad que coincida con las llaves del objeto. por ejemplo la llave 'login'. Para que asi mantengamos los formularios en un solo archivo.

export const forms: { [x: string]: InputProps[] } = {    login: [        // ...    ],    // register:[        //...     // ],    // etc...}

Otra opcin tambin es pasar el formulario completo a la funcin.

Pero nosotros lo haremos pasando solo la llave.

Asi que creamos la funcin, dentro inicializamos dos variables:

  • initialValues, loas valores iniciales de nuestro formulario.
  • validationsFields, las reglas de validacin de cada campo de nuestro formulario.

Dichas variables se volvern a reasignar por eso usamos let y las inicializamos como un objeto vaci.

type Form = 'login'export const getInputs = (section: Form) => {    let initialValues: { [key: string]: any } = {};    let validationsFields: { [key: string]: any } = {};};

Luego recorremos el formulario, accediendo a su seccin

import { forms } from './forms';type Form = 'login'export const getInputs = (section: Form) => {    let initialValues: { [key: string]: any } = {};    let validationsFields: { [key: string]: any } = {};    for (const field of forms[section]) {}};

Dentro del ciclo:
1 - Usamos la variables initialValues para computar el name del campo y asignarle el valor por defecto del campo.

2 - Haremos una condicin donde si no existe alguna validacin para el campo, solo colocamos continue para que solo salga del ciclo pero ejecute el resto del cdigo que esta despus del ciclo for of.

3 - Despus usamos la funcin para generar el esquema de validaciones que creamos anteriormente. y lo asignamos a una constante.

4 - Al final del ciclo, usamos la variable validationsFields para computar el name del campo y asignarle el esquema generado que esta en la variable schema.

5 - Finalmente, despus del ciclo retornamos un objeto con las reglas de validacin, los valores iniciales del formulario y los inputs.

import { forms } from './forms';type Form = 'login'export const getInputs = (section: Form) => {    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,        inputs: forms[section],    };};

Todo el archivo se vera asi (pueden separarlo en diferentes si quieren)

import * as Yup from "yup";import { AnyObject } from "yup/lib/types";import { forms, InputProps } from './forms';type YupBoolean = Yup.BooleanSchema<boolean | undefined, AnyObject, boolean | undefined>type YupString = Yup.StringSchema<string | undefined, AnyObject, string | undefined>const generateValidations = (field: InputProps) => {    let schema = Yup[field.typeValue ? 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;            default: schema = schema.required(rule.message); break;        }    }    return schema}type Form = 'login'export const getInputs = (section: Form) => {    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,        inputs: forms[section],    };};

Usando la funcin getInputs.

Volvemos al archivo src/pages/FormikDynamic.tsx y fuera del componente usamos nuestra funcin getInputs, mandando la seccin que queremos como parmetro, y obteniendo los valores retornados

const { initialValues, inputs, validationSchema } = getInputs('login')

Los valores iniciales y el esquema de validacin se los asignamos al componente Formik

<Formik    initialValues={initialValues}    validationSchema={validationSchema}    onSubmit={(values) => { console.log(values) }}>// ...

Ahora dentro del componente Form, vamos a iterar con la funcin map, los inputs que obtenemos del getInputs.

Vamos a evaluar el tipo de input que vamos a renderizar usando un switch. Y dependiendo de cada tipo, renderizamos un componente y le pasaremos las props necesarias, en esto nos ayudara TypeScript.

<Form noValidate>{    inputs.map(({ name, type, value, ...props }) => {        switch (type) {            case "select":                return <CustomSelect                    key={name}                    label={props.label!}                    name={name}                    options={props.options!}                />            case "radio-group":                return <CustomRadioGroup                    label={props.label!}                    name={name}                    options={props.options!}                    key={name} />            case "checkbox":                return <CustomCheckBox                    label={props.label!}                    key={name}                    name={name}                />            default:                return <CustomTextInput                    key={name}                    name={name}                    placeholder={props.placeholder}                    type={type}                />    }})}

El componente quedara asi :

import { Form, Formik } from "formik"import { CustomCheckBox, CustomRadioGroup, CustomTextInput, CustomSelect, Layout } from "../components"import { getInputs } from "../utils"const { initialValues, inputs, validationSchema } = getInputs('login')export const FormikDynamic = () => {    return (        <Layout title="Formik Dynamic">            <Formik                {...{ initialValues, validationSchema }}                onSubmit={(values) => { console.log(values) }}            >                {                    () => (                        <Form noValidate>                            {                                inputs.map(({ name, type, value, ...props }) => {                                    switch (type) {                                        case "select":                                            return <CustomSelect                                                key={name}                                                label={props.label!}                                                name={name}                                                options={props.options!}                                            />                                        case "radio-group":                                            return <CustomRadioGroup                                                label={props.label!}                                                name={name}                                                options={props.options!}                                                key={name} />                                        case "checkbox":                                            return <CustomCheckBox                                                label={props.label!}                                                key={name}                                                name={name}                                            />                                        default:                                            return <CustomTextInput                                                key={name}                                                name={name}                                                placeholder={props.placeholder}                                                type={type}                                            />                                    }                                })                            }                            <button className="btn btn_submit" type="submit">Submit</button>                        </Form>                    )                }            </Formik>        </Layout>    )}

Y listo asi tendiramos un formulario dinamice, solo modificamos el archivo form. para agregar otro formulario u otro campo a un formulario, agregar otra regla, sin tener que modificar el componente.

Tambin, puedes dividir el componente FormikDynamic en componentes mas pequeos si quieres.

Conclusin.

Implementar formularios dinmicos es de mucha ayuda si tu aplicacin tiene varios formularios, y gracias a la librera de Formik y las validaciones con Yup, es mucho ms fcil trabajar dicha situacin de administracin de formularios.

Otra idea que te puedo dar es, imaginarte que tienes una API que te da un JSON con los campos y validaciones, puede ser mucho ms util, en vez de tener un archivo con todo la informacin del formulario.

Espero que te haya gustado esta publicacin y que te haya ayudada a entender ms sobre como realizar formularios dinmicos con React y Formik.

Si es que conoces alguna otra forma distinta o mejor de realizar esta funcionalidad con gusto puedes comentarla .

Te invito a que revises mi portafolio en caso de que ests interesado en contactarme para algn proyecto! Franklin Martinez Lucas

No olvides seguirme tambin en twitter: @Frankomtz361

Cdigo fuente.

GitHub logo Franklin361 / dynamic-form

Create dynamics form with React and Formik

Formularios dinmicos con Formik y React JS.

This time, we are going to create dynamic forms using React JS and Formik!

demo

Features

  1. Show on the form
  2. Create dynamic forms
  3. Field validations

Technologies

  • React JS (v 18)
  • Vite JS
  • TypeScript
  • Formik
  • CSS vanilla

Installation

  1. Clone the repository (you need to have Git installed).
    git clone https://github.com/Franklin361/dynamic-form
  1. Install dependencies of the project.
    npm install
  1. Run the project.
    npm run dev

Article links

Here's the link to the tutorial in case you'd like to take a look at it! eyes


Original Link: https://dev.to/franklin030601/formularios-dinamicos-con-formik-y-react-js-4hne

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To