Validar formularios con JavaScript y Yup

Aprende a validar formularios con JavaScript y Yup. Una librería para navegador y NodeJS para parsear y evaluar datos de formularios.

https://github.com/jquense/yup Yup is a JavaScript schema builder for value parsing and validation.

En la última parte de éste escrito, he preparado un ejercicio práctico para ver cómo implementar la librería. Haz click en la siguiente imagen para ver el resultado final en una ventana nueva:

Validar formularios con Yup
Haz click sobre la imagen para abrir el ejercicio en una ventana nueva. Validar formularios con JavaScript y Yup

¿Quieres ir directamente al tutorial de este ejemplo?, Saltar directamente al tutorial

Revisar los datos del usuario, una acción imprescindible

Desde siempre, desarrollar proyectos web ha estado fuertemente vinculado a la creación y manipulación de formularios.

A pesar de eso, los formularios de aplicaciones web actuales, nada tienen que ver con los aburridos e impersonales de hace una década. Aquellos documentos repletos de campos dentro de tablas, sin apenas diseño ni funcionalidades, ya son parte del pasado.

La evolución que han experimentado los navegadores, ha ido acompañada de una mejora en las interfaces gráficas, y en la experiencia de los usuarios.

Y por supuesto, eso también ha tenido un impacto en los formularios web. Hoy en día, son mucho más usables, gracias a los estilos, las animaciones CSS, y recursos JavaScript.

Sin embargo, hay algo que no ha cambiado con el tiempo. Todos los datos que una persona rellena y envía a través de una interfaz, se siguen teniendo que comprobar. No hacerlo sería potencialmente peligroso, ya que un usuario malintencionado, podría tratar de aprovechar una vulnerabilidad en nuestro código.

escanear formularios
Escanear formularios para detectar problemas con los datos

Independientemente de cuán trabajada esté la UX/UI, la información que provee un usuario tiene que pasar por una fase de chequeo, tanto a nivel frontend, como backend.

Por eso, en este artículo aprenderemos a usar Yup. Una librería para validar formularios con JavaScript, que da soporte tanto a aplicaciones de navegador, como a servidores con NodeJs

Con Yup, podremos definir un conjunto de reglas, y averiguar si los datos introducidos por el usuario las cumplen, o no. Para ello, su API provee de un sistema de creación de esquemas

Más adelante veremos qué es un esquema, cómo construirlo, y cómo implementarlo para comprobar nuestro formulario.

Una herramienta declarativa e intuitiva

Pero antes, cabe preguntarse ¿Qué es lo que hace a Yup especial?

Existen miles de librerías para verificar formularios, entonces, ¿por qué elegir ésta en concreto?

Yup es una librería altamente declarativa. Tal y como veremos, su API es muy intuitiva, y fácil de seguir.

En cierto modo, me recuerda a la librería SuperExpressive, un recurso que ya analizamos en este otro post.

Crear regex con JavaScript y SuperExpressive

Otro factor importante a tener en cuenta, es su gran popularidad. Su repositorio de Github tiene más de 17K estrellas. Se encuentra activamente mantenido, y tiene un volumen medio de 3.287.339 descargas semanales.

Para integrar ésta herramienta en cualquier proyecto JavaScript, es necesario instalarla mediante el siguiente comando NPM

npm i yup

Una vez  la librería está instalada, solo queda importar las funciones que ofrece, y construir con ellas un esquema de validación.

Pero ¿qué es un esquema de validación?

Si estás familiarizado con el concepto DTO, o has utilizado lenguajes tipados como TypeScript, te resultará fácil entender qué es un esquema.

En esencia, es una representación de la estructura y tipo de datos a validar. Dicho de otro modo, se trata de describir la forma de la variable y sus restricciones, independientemente de su contenido.

Con un ejemplo se ve claramente. Supongamos que necesitamos crear un esquema que valide usuarios como el siguiente:

const user = {
  name: “Thomas A. Anderson”,
  alias: “Neo”,
  age: 37,
  email: neo@matrix.com,
  isChosenOne: true,
}

En este caso, se trata de un objeto con dos propiedades de tipo “string” (name y alias), una de tipo numérico (age), y una booleana (isChosenOne).

Sabiendo eso, con Yup definimos el esquema de la siguiente forma:

import { object, string, number, boolean } from "yup";
const schema = object({
  name: string(),
  alias: string(),
  age: number(),
  email: string(),
  isChosenOne: boolean(),
});

Adicionalmente, Yup ofrece la posibilidad de concatenar cada tipo de dato, con otros métodos para definir ciertas normas. 

Algunas de las restricciones que existen son las listadas a continuación: 

  • required: Para especificar que un determinado dato es obligatorio. 
  • min y max: Para limitar el mínimo y máximo de un valor numérico o de longitud de carácteres. 
  • positive o negative: Por si se requiere que un número sea solo mayor que cero (o solo menor que cero).
  • email o url: Para definir que un texto tenga el formato adecuado de correo o url.

Muchas de estas instrucciones admiten un texto como parámetro para customizar el mensaje de error.

Si aplicamos dichas normas al ejemplo anterior, el resultado sería parecido a este:

import { object, string, number, boolean } from "yup";
const schema = object({
  name: string()
    .required("El campo nombre es obligatorio")
    .min(1, "El nombre tiene que tener al menos un carácter")
    .max(100, "El nombre no puede superar los 100 carácteres"),
  alias: string().optional(),
  age: number()
    .required("La edad es obligatoria")
    .positive("La edad tiene que ser positiva")
    .max(90, "La edad no puede superar los 90"),
  email: string()
    .required("El email es obligatorio")
    .email("El email no tiene un formato válido"),
  isChosenOne: boolean(),
});

Por supuesto, existen muchas otras restricciones más. El siguiente enlace lleva a la documentación donde se listan todas las opciones disponibles.

https://github.com/jquense/yup#table-of-contents

Una vez construido el esquema, se ejecuta el método “validate”, pasando como primer argumento la variable a verificar.

try{
   await schema.validate(user);
}catch(err){
  // Los datos no han pasado la validación
}

Es importante destacar que “validate” devuelve una “promise”. Y en caso de que no pase la validación, Yup lanzará un error en el tiempo de ejecución. Por ese motivo es necesario encapsular el código en un bloque “try / catch”.

Validar formularios de recetas con JavaScript y Yup

La mejor forma de aprender es poniéndolo en práctica. De modo que a continuación, vamos a crear un ejercicio para validar formularios con JavaScript y Yup.

Crearemos una interfaz web, donde los usuarios podrán generar recetas de cocina a través de un formulario. Llamaremos al proyecto “Receta-maker”. Para que puedas seguir cómodamente el tutorial, te dejo un enlace al proyecto en Github.

https://github.com/Danivalldo/libreriasjs/tree/master/Yup

Para que el formulario tenga un aspecto más profesional, instalaremos “Bulma” y Material Design Icons. Bulma es un framework CSS moderno y customizable. Material Design Icons, es un set de iconos.

npm install bulma
npm install @mdi/font

Seguidamente, preparamos la estructura HTML. Todos los elementos que la conforman se encuentran dentro de un contenedor principal con la clase “.main-container”.

Los dos primeros contenedores son sencillamente introductorios. Contienen una imagen, título y descripción.

<div class="banner-container">
  <img src="undraw_cooking_lyxy_2.svg" alt="" />
</div>
<section class="hero">
  <div class="hero-body has-text-centered">
    <p class="title has-text-primary">Receta-maker</p>
    <p class="subtitle">
      Despierta el chef que llevas dentro creando deliciosas recetas, y
      compartiendolas con otros entusiastas de la cocina de todo el
      mundo.🥘
    </p>
  </div>
</section>

Inmediatamente después, ya incluimos el formulario con la clase “recipe-form”. La estructura de éste consta de múltiples bloques con la clase “field”, e inputs en su interior. El siguiente código muestra solo un ejemplo de input, puedes ver el resto en el enlace al repositorio.

<div class="field">
  <label class="label has-text-primary has-text-primary"
    >Nombre</label
  >
  <div class="control has-icons-left">
    <input
      class="input"
      type="text"
      placeholder="p. ej Dorada al horno"
      name="title"
      value=""
    />
    <span class="icon is-small is-left">
      <i class="mdi mdi-noodles"></i>
    </span>
  </div>
</div>

Antes de cerrar el formulario, incluiremos algunos bloques que servirán para informar al usuario sobre el resultado de la validación.

<div class="errors-list has-text-danger has-background-danger-light my-3 p-3 is-hidden">
  <ul>
    <li>Error descripción aquí!</li>
  </ul>
</div>
<div class="success-feedback has-text-success has-background-success-light my-3 p-3 is-hidden">
  Receta guardada
</div>
<div class="field has-text-centered">
  <button class="button is-medium is-primary" type="submit">
    Guardar
  </button>
</div>

El siguiente paso es dotar de estilos la interfaz. Para ello utilizaremos SCSS. Empezando por importar “Material design Icons”, Bulma y un archivo propio para modificar el color corporativo.

@import "../../node_modules/@mdi/font/css/materialdesignicons.min.css";
@import "./override-colors.scss";
@import "bulma/bulma.sass";

Override-colors solo altera la variable de color $primary

$primary: #5b48d9;

El resto de estilos son muy sencillos, ya que la mayor parte del trabajo lo hace el entorno de trabajo CSS.

body {
  margin: 0;
  padding: 20px 0 20px 0;
  background-image: url("../assets/images/restaurant_icons.png");
}
.main-container {
  background-color: rgba(255, 255, 255, 0.74);
  max-width: 800px;
  padding: 0 20px 50px 20px;
  margin: 20px auto;
  border-radius: 20px;
  .banner-container {
    img {
      display: block;
      max-width: 400px;
      margin: 0 auto;
    }
  }
}

Finalmente, programamos el comportamiento con JavaScript y Yup. 

Para simplificar las cosas, he preparado una clase llamada RecipeManagement que se encarga de actualizar la UI del formulario. No entraremos a ver en detalle el código de esta clase ya que escapa al propósito de este tutorial. Solo mencionar que, al instanciarla, se debe pasar una función que se ejecutará cuando el usuario trate de enviar el formulario.

Esa función recibe como parámetro el objeto “recipe”, un JSON que con los datos para validar. Por supuesto, puedes utilizar cualquier framework UI que desees (p. ej Angular, React o Vue).

Empezamos el script importando las funciones de Yup que necesitamos.

import { object, string, number, array, boolean } from "yup";

También importamos, “RecipeManagement” y los estilos SCSS.

import "./SCSS/index.scss";

Declaramos el esquema tal y como hemos aprendido antes.

const schema = object({
  title: string()
    .required("El nombre del plato es obligatorio")
    .min(3, "El nombre tiene que tener almenos 3 letras"),
  description: string()
    .required("La descripción es obligatoria")
    .min(5, "La descripción debe tener almenos 5 letras"),
  image: object({
    size: number()
      .required("La imagen es obligatoria")
      .max(58753, "La imagen no puede superar los 59Kb "),
    type: string()
      .required("La imagen es obligatoria")
      .oneOf(
        ["image/png", "image/jpg", "image/jpeg", "image/gif"],
        "El formato de la imagen tiene que ser jpg, png, jpeg o gif"
      ),
  }).required(),
  difficulty: number()
    .typeError("El valor de dificultad tiene que ser numérico")
    .required("El nivel de dificultad es obligatorio")
    .positive("El nivel de dificultad tiene que ser entre 1 y 5")
    .min(1, "El nivel de dificultad tiene que ser entre 1 y 5")
    .max(5, "El nivel de dificultad tiene que ser entre 1 y 5"),
  numDiners: number()
    .typeError("El valor de comensales tiene que ser numérico")
    .required("El número de comensales es obligatorio")
    .positive("El número de comensales que ser entre 1 y 10")
    .min(1, "El número de comensales que ser entre 1 y 10")
    .max(10, "El número de comensales que ser entre 1 y 10"),
  ingredients: array()
    .of(
      object({
        id: number().required(),
        name: string().required("Debes darle un nombre al ingrediente"),
        quantity: number()
          .required()
          .typeError("La cantidad de ingrediente tiene que ser numérico")
          .positive("La cantidad de ingrediente tiene que ser mínimo de 1")
          .min(1, "La cantidad de ingrediente tiene que ser mínimo de 1"),
      })
    )
    .min(1, "Tienes que añadir almenos un ingrediente"),
  instructions: string()
    .required("Las instrucciones son obligatorias")
    .min(5, "Las instrucciones tienen que tener almenos 5 letras"),
  acceptance: boolean().isTrue("Tienes que aceptar las condiciones de uso"),
});

Por último, creamos la función de callback que se ejecuta al enviar el formulario. En esta función tratamos de verificar que los datos de la receta pasan el filtro del esquema.

Si se cumple el proceso de comprobación, ejecutamos un bloque de actualización de la interfaz. En caso contrario, extremos la información de los errores, y los utilizamos para actualizar la UI,

const handleOnSubmit = async (recipe) => {
  try {
    await schema.validate(recipe, {
      abortEarly: false,
    });
  } catch (err) {
    const validationErrors = {};
    err.inner.forEach((error) => {
      if (error.path && !validationErrors[error.path]) {
        validationErrors[error.path] = error.message;
      }
    });
    return recipeManagement.showErrors(validationErrors);
  }
  document.querySelector('button[type="submit"]').classList.add("is-loading");
  document.querySelector(".success-feedback").classList.add("is-hidden");
  window.setTimeout(() => {
    document
      .querySelector('button[type="submit"]')
      .classList.remove("is-loading");
    document.querySelector(".success-feedback").classList.remove("is-hidden");
  }, 500);
};

Cerramos el script pasando el callback al controlador, en el momento de instanciarlo.

const recipeManagement = new RecipeManagement(handleOnSubmit);

Todo es más sencillo si se comprenden los conceptos clave

Este recurso es extremadamente sencillo si se comprenden los conceptos clave. Espero que este rápido vistazo te haya servido para resolver tus dudas en relación a cómo validar formularios con JavaScript y la librería Yup.

Por cierto, si te preocupa de que los usuarios suban material inapropiado a través de campos de subida de imágenes. Te dejo un enlace a una herramienta para detectar contenido NSFW con «machine learning».

Detectar y filtrar imágenes eróticas con NSFWjs

El listado de a continuación contiene algunos enlaces a recursos que te pueden resultar útiles para ampliar un poco lo aprendido.

Si lo crees adecuado, te agradecería que compartieras este tutorial en tus redes sociales. Muchas gracias por tu tiempo y interés.

Nos vemos pronto, un abrazo desarrolladores.

Deja un comentario