react logo

Formik y Yup: La combinación perfecta para optimizar tus formularios en React

reactformulariosformikyupvalidaciones

Tomás Cuevas

/

Introducción

Los formularios son una parte esencial de muchas aplicaciones web, y en ReactJS, existen diversas opciones para manejar la validación y el estado de los formularios. Entre las herramientas más populares se encuentran Formik y Yup, dos bibliotecas que combinadas ofrecen una solución poderosa y eficiente para el manejo de formularios en ReactJS.

Explorar en GitHub
Demostración en línea

¿Por qué utilizar Formik y Yup?

La facilidad de uso de Formik en la creación y manejo de formularios

Una de las principales ventajas de utilizar Formik y Yup es la facilidad de uso que brindan. Formik proporciona una sintaxis intuitiva y declarativa para crear y manejar formularios en ReactJS. Con solo unas pocas líneas de código, puedes definir tus campos, establecer las validaciones y controlar el estado del formulario. Además, Formik se integra sin problemas con el ecosistema de ReactJS, lo que hace que sea fácil de aprender y utilizar.

La potente validación de datos con Yup en la integración con Formik

La validación de los datos ingresados en los formularios es crucial para garantizar la integridad y consistencia de los datos enviados por los usuarios. Aquí es donde Yup entra en juego. Yup es una biblioteca de validación de esquemas que se integra perfectamente con Formik. Proporciona una forma sencilla y declarativa de definir las reglas de validación para tus campos de formulario. Con Yup, puedes validar fácilmente el tipo de datos, aplicar reglas personalizadas, validar campos dependientes y mucho más.

Instalaciónes

Para instalar estas bibliotecas, simplemente debemos ejecutar el siguientes comandos en un proyecto de React:

npm install formik yup

Ambas bibliotecas están escritas en TypeScript, por lo que si se encuentran en un proyecto que utiliza TypeScript, no será necesario instalar los archivos de definición.

Configurar Formik para manejar nuestro formulario

En este ejemplo, vamos a utilizar el custom hook useFormik proporcionado por Formik. Este hook es una herramienta poderosa y completa que ofrece una amplia gama de opciones y configuraciones para nuestros formularios.

Al llamar a este hook, recibiremos un objeto que contiene varias propiedades útiles para diversas funcionalidades. Más adelante, exploraremos en detalle estas propiedades y su utilidad.

Utilizar useFormik

Para lograrlo, necesitamos importar el hook useFormik de la biblioteca formik y luego utilizarlo en nuestro componente. A continuación, se presenta un ejemplo de cómo hacerlo:

import { useFormik } from "formik";

export const Form = () => {
  const formik = useFormik();

  return {
    /* Renderizado del formulario... */
  };
};

Estableciendo valores iniciales en useFormik

Al utilizar useFormik, puedes pasar un objeto de opciones como argumento, donde una de las propiedades clave es initialValues. Esta propiedad se utiliza para establecer los valores iniciales de los campos del formulario.

const formik = useFormik({
  initialValues: {},
});

En este ejemplo, implementaremos un formulario que simula la gestión de una lista de tareas '(ToDo)' y tendrá las siguientes propiedades:

  • title: Titulo del 'ToDo'.
  • description: Descripcion del 'ToDo'.
  • status: Estado del 'ToDo'. El cual puede ser 'pending' (pendiente), 'in-progress' (en progreso) o 'completed' (completado). Por defecto, se establecera 'pending'.
  • labels: Etiquetas de colores que podran tener los 'ToDo'. Por ejemplo: 'green', 'blue', etc
const formik = useFormik({
  initialValues: {
    title: "",
    description: "",
    status: "pending",
    labels: [],
  },
});

Estableciendo valores iniciales en los campos del formulario

Una vez que hemos establecido los valores iniciales de nuestro formulario, es necesario vincular esos valores a los diferentes inputs del formulario.

Para lograrlo, en los inputs de tipo text, debemos establecer el atributo name del input con el nombre de la propiedad dentro de initialValues. Esto permite establecer una conexión entre los campos del formulario y los valores correspondientes en el estado interno de useFormik.

En un formulario, el atributo value de los inputs se utiliza para mostrar el valor actual del campo del formulario. Cuando estamos utilizando useFormik, es fundamental vincular el atributo 'value' con el valor correspondiente en el estado interno de useFormik. Podemos obtener este valor utilizando la propiedad 'values' del objeto devuelto por useFormik.

export const Form = () => {
  const formik = useFormik({
    initialValues: {
      title: "",
      description: "",
      status: "pending",
      labels: [],
    },
  });

  return (
    <form>
      <input
        type="text"
        name="title"
        value={formik.values.title}
        placeholder="Título"
      />
      <input
        type="text"
        name="description"
        value={formik.values.description}
        placeholder="Descripción"
      />

      {/* Resto del formulario... */}
    </form>
  );
};

Para establecer los campos relacionados con la propiedad status, utilizaremos tres inputs de tipo radio. Todos estos inputs tendrán el mismo valor en el atributo name, que en este caso es 'status'.

Cada input de tipo radio tendrá un atributo value que representará el valor correspondiente a la propiedad status cuando esté seleccionado. Es importante destacar que solo se puede seleccionar uno de los inputs de tipo 'radio'.

export const Form = () => {
  const formik = useFormik({
    initialValues: {
      title: "",
      description: "",
      status: "pending",
      labels: [],
    },
  });

  return (
    <form>
      {/* Inputs de tipo text... */}

      <input type="radio" name="status" value="pending" />
      <input type="radio" name="status" value="in-progress" />
      <input type="radio" name="status" value="completed" />

      {/* Inputs de tipo checkbox... */}
    </form>
  );
};

Por último, los inputs asociados a la propiedad labels serán de tipo checkbox. Estos inputs tendrán el valor labels en el atributo name y el valor correspondiente en el atributo value. Cuando un checkbox esté seleccionado, su valor se agregará al array de labels.

En el siguiente ejemplo, contaremos con cinco opciones de colores para las etiquetas:

export const Form = () => {
  const formik = useFormik({
    initialValues: {
      title: "",
      description: "",
      status: "pending",
      labels: [],
    },
  });

  return (
    <form>
      {/* Resto del formulario... */}

      <input type="checkbox" name="labels" value="green" />
      <input type="checkbox" name="labels" value="blue" />
      <input type="checkbox" name="labels" value="red" />
      <input type="checkbox" name="labels" value="yellow" />
      <input type="checkbox" name="labels" value="violet" />
    </form>
  );
};

Actualizar los valores del formulario

Para actualizar los valores de los inputs, simplemente debemos utilizar la propiedad handleChange proporcionada por el objeto retornado por useFormik y asignarla al atributo onChange de nuestros inputs.

export const Form = () => {
  const formik = useFormik({
    initialValues: {
      title: "",
      description: "",
      status: "pending",
      labels: [],
    },
  });

  return (
    <form>
      <input
        type="text"
        name="title"
        placeholder="Título"
        value={formik.values.title}
        onChange={formik.handleChange} ↗️  controlando el valor
      />

      {/* Resto del formulario... */}
      <input
        type="radio"
        name="status"
        value="pending"
        onChange={formik.handleChange} ↗️ controlando el valor
      />

      {/* Resto del formulario... */}
      <input
        type="checkbox"
        name="labels"
        value="green"
        onChange={formik.handleChange} ↗️ controlando el valor
      />

      {/* Resto del formulario... */}
    </form>
  );
};

Manejar el envío de nuestro formulario

Dentro del objeto de configuración de useFormik, es necesario incluir una propiedad llamada onSubmit. Esta propiedad debe ser una función encargada de manejar la lógica del formulario cuando se realice el envío. La función onSubmit recibe como parámetro un objeto que contiene todos los valores establecidos en la propiedad initialValues.

const formik = useFormik({
  initialValues: {
    title: "",
    description: "",
    status: "pending",
    labels: [],
  },
  onSubmit: (formValues) => {
    console.log(formValues);
  },
});

Una vez hayas declarado la función onSubmit, es necesario manejar el evento submit del formulario. Para lograr esto, asignaremos la propiedad handleSubmit proporcionada por Formik al atributo onSubmit del formulario.

Para ejecutar esta función, debemos agregar un botón con el atributo type="submit". Al hacer clic en este botón, el formulario se enviará y se ejecutará la función declarada en la propiedad onSubmit del formulario, en este caso, formik.handleSubmit.

export const Form = () => {
  const formik = useFormik({
    initialValues: {
      title: "",
      description: "",
      status: "pending",
      labels: [],
    },
    onSubmit: (formValues) => {
      console.log(formValues);
    },
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      {/* Resto del formulario... */}
      <button type="submit">Enviar formulario</button>
    </form>
  );
};

En este caso, al completar los campos del formulario y hacer clic en el botón, los datos del formulario se mostrarán en la consola.

{
  title: "Realizar un articulo acerca de Formik y Yup";
  description: "";
  status: "in-progress";
  labels: ["green"];
}

Validando eficientemente formularios con Yup

Formik y Yup funcionan de manera excelente en conjunto. En este caso, useFormik acepta una propiedad llamada validationSchema, la cual permite definir un esquema de validación para el formulario. En dicho esquema se establecen reglas de validación para los campos del formulario.

Implementar Yup junto a useFormik

Para utilizar Yup en nuestro componente, primero debemos importarlo.

import * as Yup from "yup";

Dentro de la configuración de useFormik, es necesario definir la propiedad validationSchema y utilizar Yup junto con el método object:

const formik = useFormik({
  initialValues: {}, // Valores iniciales del formulario...
  validationSchema: Yup.object(), // Esquema de validación del formulario...
  onSubmit: (formValues) => {
    console.log(formValues);
  },
});

El método object de Yup se utiliza para definir un esquema de validación para objetos. Mediante métodos encadenados, se pueden especificar las reglas de validación para cada campo del formulario. Algunos de los métodos comunes que se utilizan junto con object incluyen 'string', 'required', 'nullable', 'oneOf', 'min', 'max', entre otros.

Agregar validaciones para los diferentes campos del formulario

Dentro del método Yup.object(), definiremos un objeto que contendrá todas las validaciones requeridas para nuestro formulario. Comenzaremos configurando las reglas de validación para el campo title:

const formik = useFormik({
  initialValues: {
    title: "",
  },
  validationSchema: Yup.object({
    title: Yup.string().min(1).max(20).required(), // Validaciones al campo 'title'
    // Resto de validaciones...
  }),
  onSubmit: (formValues) => {
    console.log(formValues);
  },
});

En esta primera validación, indicamos que el campo title debe ser de tipo 'string', tener como mínimo 1 carácter, como máximo 20 caracteres y que el campo sea obligatorio.

Ahora, procedamos a definir las reglas de validación para el resto de los campos:

const formik = useFormik({
  initialValues: {
    title: "",
    description: "",
    status: "pending",
    labels: [],
  },
  validationSchema: Yup.object({
    title: Yup.string().min(1).max(20).required(),
    description: Yup.string().min(1).max(300),
    status: Yup.string()
      .oneOf(["pending", "in-progress", "completed"])
      .required(),
    labels: Yup.array().of(Yup.string()),
  }),
  onSubmit: (formValues) => {
    console.log(formValues);
  },
});

Continuando con las validaciones restantes, se requiere que el campo description sea de tipo 'string' y contenga como máximo 300 caracteres. Este campo no es obligatorio.

Por otro lado, el campo status también debe ser de tipo 'string', pero solo se permiten los valores 'pending', 'in-progress' o 'completed'. Esta restricción se especifica mediante el método oneOf(), que recibe como argumento un arreglo con los valores permitidos. Además, este campo es obligatorio y su valor por defecto proporcionado en initialValues es pending.

Finalmente, el campo labels se define como un 'array' de 'strings', pero no es obligatorio.

Mostrar errores de validación en nuestro formulario

Para obtener los errores generados por las validaciones de Yup, podemos acceder a la propiedad errors dentro del objeto formik. Esta propiedad es un objeto que contiene todos los errores de validación. Por lo tanto, debemos utilizar esta propiedad para mostrar los mensajes de error en nuestro formulario, de modo que el usuario pueda verlos.

export const Form = () => {
  const formik = useFormik({
    initialValues: {
      title: "",
      description: "",
      status: "pending",
      labels: [],
    },
    validationSchema: Yup.object({
      title: Yup.string().min(1).max(20).required(),
      description: Yup.string().min(1).max(300),
      status: Yup.string()
        .oneOf(["pending", "in-progress", "completed"])
        .required(),
      labels: Yup.array().of(Yup.string()),
    }),
    onSubmit: (formValues) => {
      console.log(formValues);
    },
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <label>
        <input
          type="text"
          name="title"
          placeholder="Título"
          value={formik.values.title}
          onChange={formik.handleChange}
        />

        {/* error relacionados al título */}
        <span>{formik.errors.title}</span>
      </label>

      <label>
        <input
          type="text"
          name="description"
          placeholder="Descripción"
          value={formik.values.description}
          onChange={formik.handleChange}
        />

        {/* errores relaciones a la descripción */}
        <span>{formik.errors.description}</span>
      </label>

      {/* Resto del formulario... */}

      {/* Deshabilitar botón si hay algún error */}
      <button type="submit" disabled={Object.keys(formik.errors).length > 0}>
        Guardar
      </button>
    </form>
  );
};

Como se puede observar, todos los elementos <input> están envueltos en una etiqueta <label>. Junto con el <input>, se agrega un elemento <span> cuyo valor será el mensaje de error correspondiente a la propiedad formik.errors.(propiedad).

Por último, se establece el botón <button type='submit'> con el atributo disabled. Si el objeto formik.errors tiene alguna propiedad interna, el valor de 'disabled será true; de lo contrario, será false. Esto se logra utilizando el método Object.keys() para obtener un array con todas las claves del objeto formik.errors. Luego, se verifica si la longitud de este array es mayor a 0 para determinar si existe algún error. Si es así, el atributo 'disabled' se establece como 'true'; de lo contrario, se establece como 'false'.

Mensajes de error personalizados

Por defecto, cada uno de los métodos encadenados de Yup proporciona un mensaje de error cuando no se cumple la validación correspondiente. Sin embargo, es posible modificar estos mensajes personalizándolos a través de argumentos adicionales. Estos argumentos deben ser cadenas de texto que contengan los nuevos mensajes de error a mostrar cuando la validación no se cumpla.

En aquellos métodos que no requieran argumentos adicionales, el mensaje de error se establece como el primer argumento. En los métodos que sí requieran argumentos, simplemente se debe agregar una coma y, como segundo argumento, se debe proporcionar el nuevo mensaje de error.

const formik = useFormik({
  initialValues: {
    title: "",
    description: "",
    status: "pending",
    labels: [],
  },
  validationSchema: Yup.object({
    title: Yup.string()
      .max(20, "El título puede tener como máximo 20 carácteres.")
      .required("El título es requerido."),
    description: Yup.string().max(
      300,
      "La descripción puede tener como máximo 300 carácteres."
    ),
    status: Yup.string()
      .oneOf(["pending", "in-progress", "completed"])
      .required("El estado del 'ToDo' es obligatorio."),
    labels: Yup.array().of(Yup.string()),
  }),
});

En el ejemplo anterior, se han agregado mensajes de error personalizados en los campos title, description y status.

  • title: Se ha proporcionado un mensaje de error personalizado en los métodos de validación max y required.
  • description: Se ha agregado un mensaje de error personalizado en el método de validación max.
  • status: Se ha agregado un mensaje de error personalizado en el método de validación required.

De esta manera, es posible adaptar los mensajes de error a los requerimientos específicos del formulario.

Validar formulario cuando se monta

Cuando se monta (renderiza) un formulario por primera vez, por defecto no se realiza la validación. Esto puede resultar inconveniente si queremos deshabilitar el botón submit del formulario o mostrar mensajes de error para aquellos campos que no cumplan con las validaciones.

Para lograr esto, simplemente debemos agregar la propiedad validateOnMount con el valor true. De esta manera, el formulario se validará al ser renderizado por primera vez, por lo que si los valores iniciales no cumplen con las validaciones, los errores correspondientes se establecerán en el objeto formik.error.

const formik = useFormik({
  validateOnMount: true,
  // Resto de las opciones de useFormik...
});

Validar formulario cuando se modifican los campos

Al igual que la característica anterior mencionada, esta también viene desactivada por defecto. Para activarla, debemos establecer la propiedad validateOnChange con el valor true.

const formik = useFormik({
  validateOnMount: true,
  // Resto de las opciones de useFormik...
});

Al hacer esto, cada vez que el usuario modifique algún valor del formulario, este se volverá a validar y, por ende, mostrará los errores de validación correspondientes en ese momento, si los hay. Si no deseas esta característica y prefieres que los errores se muestren únicamente cuando el usuario intente enviar el formulario, simplemente no utilices esta propiedad.