¿Qué dificultades nos encontramos cuando desarrollamos formularios en front? ¿Cómo podemos solventarlas cuando trabajamos con React?

La creación de formularios en el desarrollo front suele ser una tarea difícil.Son muchos los factores a tener en cuenta: cambios de los campos, validaciones, envío... Todas estas cosas aumentan notablemente el tiempo de desarrollo.

Por eso, en React, se ha popularizado la librería Formik para la gestión de formularios, que nos proporciona las herramientas suficientes para que podamos crearlos en nuestras aplicaciones de una manera rápida, sencilla y eficiente.

¿Por qué Formik y no Redux Form?

Formik es una librería declarativa, intuitiva y adaptable desarrollada por Jared Palmer. Es muy fácil de utilizar y, gracias a su reducido peso (tan solo 12.7 kB), es perfecta para nuestras aplicaciones. Gracias a ella, al desarrollar nuestros formularios, conseguiremos:

Aunque Redux Form no es una mala opción, hay dos razones importantes para decantarnos por Formik:

  1. Como dice Dan Abramov: “El estado de un formulario es por naturaleza local y no es necesario que lo tengamos en nuestro estado de Redux”.
  2. Al utilizar Redux por detrás, cada vez que pulsemos una tecla en un input (por ejemplo) todos los reductores serán invocados y, en aplicaciones grandes, podría generarnos problemas de rendimiento.

Empezando con Formik

A continuación voy a mostrar cómo instalar la librería y crear un sencillo formulario. Para instalar Formik en nuestro proyecto, tenemos dos opciones:

npm install formik --save
yarn add formik

Formulario usando el componente <Formik />

En este ejemplo, utilizaremos el componente <Formik />. La librería también pone a nuestra disposición el withFormik() HOC o el Hook useFormik(), aunque su autor nos recomienda no usarlos ya que no está pensado para la mayoría de casos de usos.

El componente <Formik /> es el que nos va a ayudar a construir nuestro formulario a través de sus props:

Como vemos en las props, tenemos 2 opciones para renderizar nuestros formularios ya que render pasa a estar deprecada desde la versión 2.x:

  1. <Formik component>
  2. <Formik children>

Ambas opciones pasan las mismas props. En nuestro ejemplo utilizaremos la opción 2, pasando como children nuestro formulario. El componente que le pasemos al children recibirá ciertas props para utilizarlas según nos convengan. Nosotros utilizaremos algunas, pero en el siguiente enlace tenéis toda la documentación: Docs <Formik />.

Empezando con el código

Vamos a empezar a ver el ejemplo y su funcionamiento con un formulario muy básico para dar de alta un jugador de baloncesto.

import React from 'react';
import { Formik } from 'formik';
import { BasicForm } from '../../components';

const validation = values => {
 let errors = {};

 if (!values.name) {
   errors.name = 'Name is required!';
 } else if (values.name.length <= 1) {
   errors.name = 'Name has to be 1 character at less!';
 }
 if (!values.lastName) {
   errors.lastName = 'Last name is required!';
 }
 if (!values.team) {
   errors.team = 'Team is required!';
 }
 else if (values.team.length < 2) {
   errors.team = 'Team is required!';
 }
 if (!values.number) {
   errors.number = 'Number is required!';
 } else if (isNaN(values.number)) {
   errors.number = 'Must be a number!';
 }
 return errors;
}

const Basic = () => (
 <Formik
   initialValues={{
     name: '',
     lastName: '',
     number: '',
     position: 'pg',
     team: '',
   }}
   onSubmit={(values, actions) => {
     setTimeout(() => {
       console.log(values)
       console.log(actions)
       actions.setSubmitting(false);
     }, 2000);
   }}
   validate={validation}
 >
   {props => <BasicForm {...props} />}
 </Formik>
);

export default Basic;

Como observamos, hemos creado un componente funcional llamado Basic que será nuestro formulario. En él renderizamos el componente <Formik /> y le pasamos 3 propiedades:

Como children, estamos pasando un componente que se llama <BasicForm /> con las props que nos provee Formik.

import React from 'react';
import InputField from '../UI/InputField';

const BasicForm = ({ handleChange, handleSubmit, isSubmitting, resetForm, values, errors, touched }) => (
 <form onSubmit={handleSubmit}>
   <div className="colums">
     <div className="column is-full">
       <div className="field">
         <label className="label">Team</label>
         <div className="control">
           <input
             name="team"
             className="input"
             onChange={handleChange}
             value={values.team}
           />
         </div>
         {touched.team && errors.team && <p className="help is-danger">{errors.team}}
       </div>
     </div>
     <div className="column is-full">
       <InputField name="name" type="text" label="Player Name" />
     </div>
     <div className="column is-full">
       <InputField name="lastName" type="text" label="Player Last Name" />
     </div>
     <div className="column is-full">
       <InputField name="number" type="text" label="Number" />
     </div>
     <div className="column is-full">
       <div className="field">
         <label className="label">Position</label>
         <div className="control">
           <div className="select is-rounded">
             <select
               id="position"
               name="position"
               onChange={handleChange}
               value={values.position}
             >
               <option value="pg">Point Guard</option>
               <option value="sg">Shooting Guard</option>
               <option value="sf">Small Forward</option>
               <option value="pf">Power Forward</option>
               <option value="c">Center</option>
             </select>
           </div>
         </div>
       </div>
     </div>
     <div className="is-full">
       <div className="field is-grouped is-grouped-centered">
         <p className="control">
           <button
             className="button is-rounded is-primary"
             disabled={isSubmitting}
             type="submit"
           >
             Submit
           </button>
         
         <p className="control">
           <button
             className="button is-rounded is-danger"
             type="button"
             onClick={resetForm}
           >
             Reset
           </button>
         
       </div>
     </div>
   </div>
 </form>
)

export default BasicForm;

En el código anterior, vemos el componente donde tenemos los diferentes campos del formulario. Obtenemos por “object destructuring” las propiedades que nos provee Formik y que vamos a necesitar.

Puede parecer muy largo pero vamos a ir explicando poco a poco los detalles que realmente nos interesan. Por un lado, hemos añadido la etiqueta <form /> a nuestro formulario pasándole al evento onSubmit la función handleSubmit que nos da Formik a través de las props. Esta será la función que se ejecutará cuando pulsemos el botón submit del formulario y que, al final, acabará ejecutando la función que pasamos a la propiedad onSubmit en el código anterior.

En los botones estamos pasándole al atributo disabled del botón submit el boolean isSubmitting que se pondrá a true en el momento que el envío del formulario esté en progreso.

También tenemos un botón de reset al que le pasamos al evento onClick la prop resetForm que reiniciará los valores del formulario.

Ahora veamos el primer input de nuestro formulario que hará referencia al valor “team”:

<div className="field">
     <label className="label">Team</label>
     <div className="control">
       <input
         name="team"
         className="input"
         onChange={handleChange}
         value={values.team}
       />
     </div>
     {touched.team && errors.team && 
          <p className="help is-danger">{errors.team}}
</div>

Lo que nos interesa de este código es el evento onChange al que le pasaremos handleChange que lo hemos obtenido de las props recibidas de Formik. HandleChange es quien se encarga de cambiar el valor para el campo en el formulario y, para ello, es necesario que el input tenga el atributo “name” o “id” con el mismo nombre que la key que pusimos en initValue.

También vamos a pintar el error del campo en caso de que no pase la validación y comprobamos si existe un error para el campo “team” dentro del objeto errors que nos provee Formik.

Si controlamos el error de esta manera, puede que su comportamiento no sea el más adecuado. Si tenemos una validación sobre la longitud del campo, marcando error si tiene una longitud menor de 2 caracteres; cuando empecemos a escribir en el campo, y la longitud del valor sea inferior a 2, nos empezará a mostrar el error.

Para evitar ese comportamiento podemos usar la propiedad touched y, dependiendo de si queremos que lo pinte antes de dar al botón submit, podemos añadir también al evento onBlur la prop handleBlur que nos pondrá el campo como touched cuando suceda dicho evento.

Creando nuestro propio Input reutilizable

Formik también nos da la posibilidad de crear nuestros propios componentes. En este ejemplo, vamos a crear un input que sea reutilizable. Para ello, tenemos un componente llamado <InputField /> que tendrá el siguiente aspecto:

import React from 'react';
import { useField } from 'formik';

const InputField = ({ label, ...props }) => {
 const [field, meta] = useField(props);
 return (
   <div className="field">
     <label className="label">{label}</label>
     <div className="control">
       <input className="input" {...field} {...props} />
     </div>
     {meta.touched && meta.error
       && <p className="help is-danger">{meta.error}}
   </div>
 )
}

export default InputField;

En este componente ya hacemos uso de un Hook, que nos dispone la librería llamado useField().

Este Hook nos va a devolver una triple tupla. En nuestro caso solo vamos a usar 2 de los 3 valores que podemos obtener en el array que son field y meta.

En field tendremos un objeto que contiene:

En meta tendremos un objeto que contiene metadata del campo:

Por tanto, al pasarle al <input /> tanto field como meta, estamos haciendo que nuestro componente de tipo input tenga dichas propiedades. No es necesario pasarle todas las propiedades (como se ve en el ejemplo) y podríamos configurarlo a nuestra manera, ya que nos puede interesar usar el evento onBlur en nuestro componente.

Gracias al uso de este Hook, tenemos la flexibilidad de poder configurar nuestros propios componentes de tipo Input, por ejemplo, y utilizarlos entre los distintos formularios que tengamos en la aplicación. Esto es una gran ventaja para no repetir código entre nuestros formularios.

Fases en el envío del formulario

Cada vez que pulsamos el botón de enviar el formulario, se ejecutarán las siguientes fases:

  1. Pre-envío:
  1. Validación:
  1. Envío:

Conclusión

Como hemos visto a través de este ejemplo básico, Formik nos va a facilitar mucho nuestros desarrollos de formularios en aplicaciones con React, proveyéndonos de una API con una documentación muy clara y con ejemplos fáciles de entender.

Mi opinión es que la probéis para comprobar vosotros mismos las posibilidades que nos da. Para no hacer muy largo este post, no hemos hablado de la validación de formulario a través de otra librería como es Yup, con la que podremos crear nuestras validaciones a través de object schemas y que la misma documentación de Formik nos dice que se integra muy bien, ya que tiene una configuración especial para ella con la prop validationSchema.

Podéis ver el ejemplo del formulario online en el siguiente link: Basic Form.

Si te ha gustado esta entrada de blog y te has quedado con ganas de algún ejemplo más usando la librería (como puede ser usando las validaciones con Yup), anímate y déjanos un comentario si quieres un post sobre esto :).

Cuéntanos qué te parece.

Los comentarios serán moderados. Serán visibles si aportan un argumento constructivo. Si no estás de acuerdo con algún punto, por favor, muestra tus opiniones de manera educada.

Suscríbete

Estamos comprometidos.

Tecnología, personas e impacto positivo.