Lo que sigue es crear un Layout, esto solo es con propósito de estética para la app, no es obligatorio.
🚨 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.
Creamos la carpeta src/components y dentro creamos el archivo Layout.tsx para agregar:
Después vamos a crear la carpeta src/pages. El propósito es simular paginas diferentes ya que vamos a explicar una forma básica en la que se suele usar formik, después otra pagina sera para explicar los formularios dinámicos
Crearemos un archivo FormikBasic.tsx para agregar por el momento lo siguiente:
initialValues, va a tener un objeto definiendo cada campo del formulario y serán 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 función 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 validación
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 función que le debes pasar a tu formulario para hacer el posteo del formulario. Esta función 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 después de que en input haya sido tocado y no al inicio de la aplicación cuando el usuario apenas ve el formulario. Esta prop, es un objeto el cual us props están identificados con el nombre de las propiedades que colocaste en initialValues.
getFieldProps, es una función getter que nos trae diferentes atributos necesarios para que el input funcione (name, value, onChange, onBlur) que generalmente también las podemos obtener del useFormik, pero seria mas código tener que colocar cada propiedad (es util cuando tenemos que hacer algo especifico con dicha propiedad. pero en este caso no). Recibe como parámetro un name, que debe con alguna propiedad de initialValues
Ahora que tenemos nuestro hook listo, pasaremos a modificar nuestro JSX.
Primero en la etiqueta form colocamos el handleSubmit y el noValidate.
<formnoValidateonSubmit={handleSubmit}>
Ahora en los input de tipo text, email y password, colocamos lo siguiente.
Esparcimos las propiedades que retorna getFieldProps (recibe como parámetro un name, que debe con alguna propiedad de initialValues).
El className hacemos la validación 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).
En el caso del input de tipo radio.
Agregamos el getFieldProps y esparcimos los valores que retorna dicha función.
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.
Todo nuestro componente luciría de la siguiente manera 👀:
import*asYupfrom'yup';import{useFormik}from"formik";import{Layout}from"../components"exportconstFormikBasic=()=>{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 (<Layouttitle="Formik Basic"><formnoValidateonSubmit={handleSubmit}><inputtype="text"placeholder="Full name"{...getFieldProps('fullName')}className={`${(touched.fullName&&errors.fullName)&&'error_input'}`}/>{(touched.fullName&&errors.fullName)&&<spanclassName="error">{errors.fullName}</span>}<inputtype="email"placeholder="E-mail"{...getFieldProps('email')}className={`${(touched.email&&errors.email)&&'error_input'}`}/>{(touched.email&&errors.email)&&<spanclassName="error">{errors.email}</span>}<inputtype="password"placeholder="Password"{...getFieldProps('password')}className={`${(touched.password&&errors.password)&&'error_input'}`}/>{(touched.password&&errors.password)&&<spanclassName="error">{errors.password}</span>}<div><labelhtmlFor="rol">Select an option:</label><selectid="rol"{...getFieldProps('rol')}><optionvalue="">--- Select ---</option><optionvalue="admin">Admin</option><optionvalue="user">User</option><optionvalue="super">Super Admin</option></select></div><divclassName='radio-group'><b>Gender: </b><label><inputtype="radio"{...getFieldProps('gender')}checked={getFieldProps('gender').value==='man'}value='man'/>
Man
</label><label><inputtype="radio"{...getFieldProps('gender')}checked={getFieldProps('gender').value==='women'}value='women'/>
Woman
</label><label><inputtype="radio"{...getFieldProps('gender')}checked={getFieldProps('gender').value==='other'}value='other'/>
Other
</label>{(touched.gender&&errors.gender)&&<spanclassName="error">{errors.gender}</span>}</div>{(touched.rol&&errors.rol)&&<spanclassName="error">{errors.rol}</span>}<label><inputtype="checkbox"{...getFieldProps('terms')}/>
Terms and Conditions
{(touched.terms&&errors.terms)&&<spanclassName="error">{errors.terms}</span>}</label><buttontype="submit">Submit</button></form></Layout>)}
Hasta aquí tenemos un uso básico y funcional de un formulario con sus validaciones.
🖊️ Diseñando nuestro formulario dinámico.
Ahora vamos a crear una nueva pagina, en nuestro src/pages creamos FormikDynamic.tsx
Y por el momento agregamos:
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. Después estableceremos el initialValues y validationSchema.
El componente Formik usa el patron de "render props" y para ello dentro del componente recibe una función. Dicha función también devolvían ciertas propiedades como lo hace el hook useFormik, pero en este caso no las usaremos.
La función, 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.
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').
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 posición el FieldProps, y esparcimos tanto las props que le llegan al componente como el valor de field que nos da el hook.
Podrían también usar la segunda posición (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.
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 también su atributo checked que tendrá una condición 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.
También 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 evalúa si el valor establecido es igual a uno de los valores del grupo de inputs entonces su atributo checked lo pondrá en trae.
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 descripción.
Aquí definiremos como queremos nuestro formulario, podría 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 básicas 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 validación.
Luego tenemos la interface Opt que ya habíamos 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 validación con Yup.
-type, es el tipo de validación que queremos implementar al campo.
-value, el valor (opcional) que estableceremos a la validación.
-message, el mensaje personalizado para mostrar.
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 básico que hemos hecho con anterioridad.
exportconstforms:{[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 función para construir cada regla de validación y los valores iniciales del formulario
🖊️ Generando las reglas de validación mediante funciones.
Vamos a src/utils y dentro creamos un archivo llamado getInputs.ts
Donde vamos a tener la primera función:
generateValidations, recibe solo un parámetro:
field, el primero es el campo con todos sus props, aunque solo necesitamos las validaciones y el tipo de valor que es el campo.
constgenerateValidations=(field:InputProps)=>{}
Después 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 demás son string, pero aceptará otros valores primitivos.
Ahora necesitamos inicializar los valores de nuestro formulario.
Para ello vamos a crear un función que recibe un parámetro.
Notaran que en el archivo src/utils/forms.ts la constante forms exporta un objeto, bueno la idea es que la función 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.
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 condición donde si no existe alguna validación para el campo, solo colocamos continue para que solo salga del ciclo pero ejecute el resto del código que esta después del ciclo for of.
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, después del ciclo retornamos un objeto con las reglas de validación, los valores iniciales del formulario y los inputs.
Volvemos al archivo src/pages/FormikDynamic.tsx y fuera del componente usamos nuestra función getInputs, mandando la sección que queremos como parámetro, y obteniendo los valores retornados
Ahora dentro del componente Form, vamos a iterar con la función 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.
Y listo asi tendiéramos 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.
También, puedes dividir el componente FormikDynamic en componentes mas pequeños si quieres.
🖊️ Conclusión.
Implementar formularios dinámicos es de mucha ayuda si tu aplicación tiene varios formularios, y gracias a la librería de Formik y las validaciones con Yup, es mucho más fácil trabajar dicha situación de administración 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 más util, en vez de tener un archivo con todo la información del formulario.
Espero que te haya gustado esta publicación y que te haya ayudada a entender más sobre como realizar formularios dinámicos 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 estés interesado en contactarme para algún proyecto! Franklin Martinez Lucas
🔵 No olvides seguirme también en twitter: @Frankomtz361