Ir al contenido
  1. Blog/

Cómo validar formularios en React, sin lágrimas

·8 mins·
Cualquier desarrollador que haya trabajado con React ha tenido ese ticket fastidioso para validar un formulario. Afortunadamente para ti, hoy te mostraré la forma más sencilla de cerrarlo: Formik.

Formik what? #

Formik es uno de los paquetes de software de código abierto más útiles que podemos encontrar en el repositorio de npm (o yarn, como gustes). En palabras de uno de sus creadores:

Admítamoslo, los formularios requiren de muchísimo código en React. Para poner las cosas peor, la mayoría de utilidades para construirlos hacen muchísima, demasiada magia que frecuentemente trae un costo al desempeño — @JaredPalmer1

Para Jared y compañía había un beneficio en estandarizar los componentes de entrada y cómo los datos fluían a través del Form. Es la razón de ser de Formik.

Preparando el escenario #

Haremos uso del famosísimo toolchain create-react-app. Ve a tu línea de comandos y ejecuta:

npx create-react-app formik-sample
cd formik-sample
npm start

React App by create-react-app

Ya tenemos la base para trabajar nuestra aplicación React, pero necesitamos un form a validar. Para eso instalaremos el siguiente paquete:

npm install bootstrap reactstrap --save

Este paquete componetiza la mayoría de utilidades del popular framework CSS Bootstrap, lo cuál acelera nuestra habilidad de construir el layout del formulario. Una vez completa la instalación, copia y pega este código en tu archivo src/App.js:

 1import React from "react";
 2import { Container, Button, Form, FormGroup, Label, Input, Card, CardBody, CardHeader, FormFeedback } from "reactstrap";
 3
 4function Formulario() {
 5  return (
 6    <Container className="p-5">
 7      <Card>
 8        <CardHeader></CardHeader>
 9        <CardBody>
10          <Form>
11            <h1>Form</h1>
12            <FormGroup>
13              <Label for="name">Name</Label>
14              <Input type="text" name="name" placeholder="Woody Allen" />
15            </FormGroup>
16            <FormGroup>
17              <Label for="email">Email</Label>
18              <Input
19                type="email"
20                name="email"
21                placeholder="contoso@domain.com"
22              />
23            </FormGroup>
24            <FormGroup>
25              <Label for="password">Password</Label>
26              <Input
27                type="password"
28                name="password"
29                placeholder="Provide a password"
30              />
31            </FormGroup>
32            <FormGroup>
33              <Label for="bio">Text Area</Label>
34              <Input type="textarea" name="bio" />
35            </FormGroup>
36
37            <Button type="submit">Submit</Button>
38          </Form>
39        </CardBody>
40      </Card>
41    </Container>
42  );
43}
44
45export default Formulario;

También, añade está línea en el archivo src/index.js:

1import React from "react";
2import ReactDOM from "react-dom";
3
4import "bootstrap/dist/css/bootstrap.css";

Deberías ver un formulario similar al de la imagen:

Form built with reactstrap

En adición a estos dos archivos, crearemos un tercero para escribir dos funciones que serán de mucha importacia al implementar Formik. srs/helpers.js:

 1export function validate(values) {
 2  const errors = {};
 3
 4  // Name
 5  if (!values.name) errors.name = "Required";
 6
 7  // Email
 8  const emailRgx = !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
 9  if (!values.email) errors.email = "Required";
10  else if (emailRgx.test(values.email)) errors.email = `Invalid email address`;
11
12  // Password
13  if (!values.password) errors.password = `Required`;
14  else if (`${values.password}`.length < 7)
15    errors.password = `Password must be larger than 7 characters`;
16
17  return errors;
18}
19
20export function onSubmit() {
21  setTimeout(() => {
22    alert(JSON.stringify(values, null, 2));
23
24    setSubmitting(false);
25  }, 250);
26}

Las cuáles importamos en srs/App.js:

1import React from "react";
2import { Container, Button, Form, FormGroup, Label, Input, Card, CardBody, CardHeader, FormFeedback } from "reactstrap";
3import { validate, onSubmit } from "./helpers.js";

Formik #

La instalación es muy sencilla, simplemente es otro paquete en npm.

npm install formik --save

Luego de instalarlo, lo importamos en src/App.js.

1import React from "react";
2import { Formik } from "formik";

Formik es el componente principal de la librería, se mantiene al tanto del estado del formulario y provee valores, métodos y manejadores de eventos a través del parámetro props. Es indispensable que el formulario sea encapsulado por esta etiqueta <Formik>. Visualicemos una implementación básica de la librería con el código siguiente:

 6function Formulario() {
 7  return (
 8    <Container className="p-5">
 9      <Card>
10        <CardHeader></CardHeader>
11        <CardBody>
12          <Formik
13            initialValues={{
14              name: ``,
15              email: ``,
16              password: ``,
17              bio: ``,
18            }}
19            validate={validate}
20            onSubmit={onSubmit}
21          >
22            {(props) => {
23              const { values, errors, touched, handleChange, handleBlur, handleSubmit, isSubmitting } = props;
24              return (
25                <Form onSubmit={handleSubmit}>
26                  <h1>Form</h1>
27                  <FormGroup>
28                    <Label for="name">Name</Label>
29                    <Input
30                      type="text"
31                      name="name"
32                      placeholder="Woody Allen"
33                      invalid={errors.name && touched.name}
34                      onChange={handleChange}
35                      onBlur={handleBlur}
36                      value={values.name}
37                    />
38                    <FormFeedback>{errors.name}</FormFeedback>
39                  </FormGroup>
40                  <FormGroup>
41                    <Label for="email">Email</Label>
42                    <Input
43                      type="email"
44                      name="email"
45                      placeholder="contoso@domain.com"
46                      invalid={errors.email && touched.email}
47                      onChange={handleChange}
48                      onBlur={handleBlur}
49                      value={values.email}
50                    />
51                    <FormFeedback>{errors.email}</FormFeedback>
52                  </FormGroup>
53                  <FormGroup>
54                    <Label for="password">Password</Label>
55                    <Input
56                      type="password"
57                      name="password"
58                      placeholder="Provide a password"
59                      invalid={errors.password && touched.password}
60                      onChange={handleChange}
61                      onBlur={handleBlur}
62                      value={values.password}
63                    />
64                    <FormFeedback>{errors.password}</FormFeedback>
65                  </FormGroup>
66                  <FormGroup>
67                    <Label for="bio">Text Area</Label>
68                    <Input
69                      type="textarea"
70                      name="bio"
71                      onChange={handleChange}
72                      onBlur={handleBlur}
73                      value={values.name}
74                    />
75                  </FormGroup>
76
77                  <Button type="submit" disabled={isSubmitting}>
78                    {isSubmitting ? `Loading` : `Submit`}
79                  </Button>
80                </Form>
81              );
82            }}
83          </Formik>
84        </CardBody>
85      </Card>
86    </Container>
87  );
88}

Para que Formik pueda mantenerse al tanto de los cambios de valores en el formulario y, por ende, ser capaz de validarlos, es necesario proveer una colección de valores iniciales, ó initialValues, que esté relacionados por nombre a los diferentes <Input>s. En el código anterior, definimos 4 controles: name, email, password y bio. Líneas 14-17, 31, 44, 57 y 70.

Es en base a esa configuración que los demás métodos y variables contenidas en props permitirán que Formik haga su magia. Línea 23.

No debemos olvidar que estamos en React, por lo qué debemos manejar el cambio de los valores en los controles. Esto lo logramos con los manejadores provistos por Formik: handleChange y handleBlur. Sin olvidar que debemos renderizar el valor adecuado en el control, para eso haremos uso de la propiedad values. Líneas 34-36, 47-49, 60-62 y 71-73.

También el tag <Formik> en sí mismo necesita de ciertas propiedades para funcionar, entre ellas:

  • initialValues: Le dice a Formik de cuales valores debe mantenerse al tanto y buscar sus respectivos controles en el formulario.
  • validate: Esta función recibe como parametro los values de los cuáles la librería está al tanto para ser validados. Línea 19.
  • onSubmit: Quizá la función que más te interese de la librería. Es aquí dónde definimos que debe suceder si nuestro formulario ha sido validado con éxito. Por ejemplo, postear los valores a un servicio del backend. Línea 20.

Al hacer clic en Submit, sin completar un solo <Input>, deberías ver algo similar a esto:

Formulario validado

Listo, ya hemos validado el formulario.

Simplifiquemos el código #

Seguramente tienes alguna de estas preguntas en tu cabeza:

  • ¿Por qué repetir la asignación de handleChange y handleBlur? y
  • ¿Por qué validar manualmente cada propiedad paso a paso?

Creéme, yo también me hice esas preguntas.

Para fortuna de quién tenga que mantener este formulario, hay una forma más sencilla de definir las validación y los controles de estado.

Formik Field #

Para reducir el número de veces que repetimos la asignación de los manejadores de eventos, vamos a importar otro elemento de la librería Formik. En el archivo src/App.js:

1import React from "react";
2import { Formik, Field } from "formik"; // + Field

Este componente nos permite ahorrarnos la asignación de manejadores y valor directamente al componente. Lo único que debemos hacer, y ya que estamos usando reacstrap, es asignarlo a la propiedad tag de cada elemento <Input>. Formik se encargará del resto.

29                    <Input
30                      type="text"
31                      name="name"
32                      placeholder="Woody Allen"
33                      invalid={errors.name && touched.name}
34                      onChange={handleChange}
35                      onBlur={handleBlur}
36                      value={values.name}
37                    />
29                    <Input
30                      type="text"
31                      name="name"
32                      placeholder="Woody Allen"
33                      invalid={errors.name && touched.name}
34                      tag={Field}
35                    />

Es imprescindible que la propiedad name del <Input> tenga el mismo nombre a una propiedad en initialValues, como lo explicamos antes.

Formik + Yup #

Yup es una librería que nos permite construir esquemas de conversión y validación de una manera sintáctica. El punto más fuerte de Yup es el nível de expresividad que las validaciones pueden alcanzar, sencillas o complejas.

De acuerdo a la documentación de Formik, Yup es un ciudadano de primera clase en la librería por lo que posee una prop especial en el elemento <Formik> llamada validationSchema.

$ npm install yup --save

Una vez instalamos Yup, lo importamos en el archivo src/App.js:

1import React from "react";
2import { Formik, Field } from "formik";
3import * as Yup from "yup"; // Importando Yup

Regresemos al código del componente Formulario en el archivo src/App.js. Justo en la propiedades del elemento <Formik> vamos a eliminar la propiedad validate y en su lugar asignaremos la propiedad validationSchema. Así:

13<Formik
14  initialValues={{
15    name: "",
16    email: "",
17    password: "",
18    bio: "",
19  }}
20  validationSchema={Yup.object().shape({
21    name: Yup.string().required("Required"),
22    email: Yup.string().email("Invalid email").required("Required"),
23    password: Yup.string().min(8, "Password is too short").required("Required"),
24  })}
25  onSubmit={onSubmit}
26>

Por último, podemos llenar nuestro formulario y ver que una vez validado, el callback en la propiedad onSubmit del elemento <Formik> es ejecutada.

Formulario completo

Antes de irme #

Espero este post te sea de mucha ayuda. En mi experiencia con React, Formik me ha facilitado el validar formularios al proveerme de métodos que me permiten ajustar a cada momento los valores y las validaciones necesarias. La expresividad del código es de gran valor ya que permite escalar y/o mantener de una forma sencilla sin reinventar la rueda para en cada escenario.

Referencias #


  1. Esta cita es parte de la introducción escrita por Jared en el sitio Formik Docs↩︎