Tu primer formulario en React con React Hook Form

Gabriel Jiménez | Hace 6 meses

Existen muchas alternativas en el mercado para crear formularios con React: React Final Form, Formik, Material UI, React Bootstrap, React Hook Form. Este último, es una opción totalmente enfocada en la creación de formularios utilizando hooks. Los hooks nos permiten reutilizar código en diferentes partes de la aplicación. Dicho esto, veamos como React Hook Form reutiliza código usando hooks para crear formularios.


¿Qué es React Hook Form?

De forma general, React Hook Form es una librería para crear formularios mediante hooks.


Entre las características que los distinguen de otras alternativas son:


  • Experiencia del desarrollador (DX). Fácil uso del API y funcionales para crear formularios a medida.
  • Estándares HTML. Se apoya de los estándares html para crear un formulario. Tanto de los elementos como las validaciones. Solo lo ajusta a su contexto.
  • Liviano. No requiere ninguna dependencia extra. Solo React y React Hook Form.
  • Rendimiento. Para lograr esto se enfoca en tres cosas: minimizar la cantidad de renders, el proceso computacional de las validaciones y que el montaje sea rápido.
  • Adoptable. Dado que el estado del formulario se almacena internamente por React Hook Form, no choca con otros estados internos de otros componentes.
  • Experiencia de usuario (UX). Aportan estrategias para mejorar la experiencia tanto de. usuario como de las validaciones.

¿Cómo implementar React Hook Form en mi proyecto React?

Para destacar las funcionalidades de React Hook Form, vamos a desarrollar un formulario únicamente utilizando React —no librerías externas—, y lo replicaremos a la par, usando React Hook Form.


Configurar ambiente

Para este proyecto, usaremos la herramienta Create React App, ya que es un proyecto didáctico.

Para crearlo, ejecutemos los siguientes comandos:

npx create-react-app ejemplo-react-hook-form
cd ejemplo-react-hook-form
npm start


NOTA: Para proyectos más serie se recomienda usar herramientas como Vite.


Al ejecutar estos comandos, tendremos un proyecto configurado y listo para ejecutarse en el puerto localhost:3000

Instalar React Hook Form

Instalar React Hook Form solo es necesario un comando.

Npm

npm install react-hook-form


Yarn

yarn install react-hook-form

Problematica

Uno de nuestros clientes en el sector fintech, necesita desarrollar un formulario para registrar las devoluciones de sus productos.


Los requerimientos son los siguientes:


  • El formulario debe poder seleccionar el producto a devolver, asignar el monto de devolución y agregar alguna observación
  • Todos los campos son obligatorios, excepto el campo observaciones.

Implementación

Comencemos con crear el formulario con React y sin apoyo de ninguna librería externa.

    1: import React, {useState} from 'react'
    2: 
    3: const DevolucionForm = () => {
    4:   const [values, setValues] = useState({
    5:     producto: '',
    6:     monto: '',
    7:     observaciones: ''
    8:   })
    9: 
   10:   function handleChange({ target }) {
   11:     const { value, name } = target;
   12:     setValues({ ...values, [name]: value });
   13:   }
   14: 
   15:   function handleSubmit() {
   16:     console.log("Formulario enviado: " + values);
   17:   }
   18: 
   19:   return (
   20:     <form onSubmit={handleSubmit}>
   21:       <select name='producto' onChange={handleChange}>
   22:         <option value="">Selecciona una opción</option>
   23:         <option value="producto a">Producto A</option>
   24:         <option value="producto b">Producto B</option>
   25:       </select>
   26:       <input type='number' name='monto' onChange={handleChange} value={values.monto} placeholder='Monto de devolucion' />
   27:       <textarea name='observaciones' onChange={handleChange} value={values.observaciones} />
   28:     </form>
   29:   )
   30: }
   31: 
   32: export default DevolucionForm


Este tipo de formularios están bien cuando nuestros proyectos son pequeños, pero para proyectos grandes no son fáciles de mantener y escalar. 


Aquí algunas de las razones:


  • Cada desarrollador decide el flujo de envío del formulario, si usar onSubmit o un clic en el botón.
  • Los estilos de los campos pueden verse distintos entre formularios.
  • Cada desarrollador decide como guardar los campos, ya sea usando un objeto o un estado por campo.
  • En cada formulario los campos se tendrán que conectar manualmente el estado values y asociar el handleChange. Esto genera código duplicado.

Veamos como React Hook Form resuelve esto:

    1: import React from "react";
    2: import { useForm } from "react-hook-form";
    3: 
    4: const DevolucionForm = () => {
    5:   const { register, handleSubmit } = useForm({
    6:     defaultValues: {
    7:       producto: "",
    8:       monto: "",
    9:       observaciones: "",
   10:     },
   11:   });
   12: 
   13:   function onSubmit(data) {
   14:     console.log("Formulario enviado:", data);
   15:   }
   16: 
   17:   return (
   18:     <form onSubmit={handleSubmit(onSubmit)}>
   19:       <select {...register("producto")}>
   20:         <option value="">Selecciona una opción</option>
   21:         <option value="producto a">Producto A</option>
   22:         <option value="producto b">Producto B</option>
   23:       </select>
   24: 
   25:       <input
   26:         type="number"
   27:         placeholder="Monto de devolución"
   28:         {...register("monto")}
   29:       />
   30: 
   31:       <textarea
   32:         placeholder="Observaciones"
   33:         {...register("observaciones")}
   34:       />
   35: 
   36:       <button type="submit">Enviar</button>
   37:     </form>
   38:   );
   39: };
   40: 
   41: export default DevolucionForm;


Más limpio. ¿Cierto?

Destaquemos varios puntos importantes:


  • Uso del hook useForm.
  • Usamos defaultValues para definir los campos de nuestro formulario.
  • La propiedad register se encarga de sincronizar manualmente lo que el usuario escribe.
  • Se elimina la asignación de la propiedad name a cada campo.
  • Se la función handleChange para sincronizar lo que el usuario escribe.
  • Pasamos nuestra función onSubmit para que se invoque una vez que, el formulario se evalúe con React Hook Form.


Continuemos con las validaciones.

Validaciones

Las validaciones para este ejercicio es que todos los campos sean obligatorios, excepto el campo observaciones.


Sin librería externa:

    1: import React, { useState } from 'react'
    2: 
    3: const DevolucionForm = () => {
    4:   const [values, setValues] = useState({
    5:     producto: '',
    6:     monto: '',
    7:     observaciones: ''
    8:   })
    9: 
   10:   const [errors, setErrors] = useState([])
   11: 
   12:   function handleChange({ target }) {
   13:     const { value, name } = target
   14:     setValues({ ...values, [name]: value })
   15:   }
   16: 
   17:   function handleSubmit(e) {
   18:     e.preventDefault()
   19:     const newErrors = []
   20: 
   21:     if (!values.producto) {
   22:       newErrors.push("El campo 'Producto' es obligatorio")
   23:     }
   24: 
   25:     if (!values.monto) {
   26:       newErrors.push("El campo 'Monto' es obligatorio")
   27:     }
   28: 
   29:     setErrors(newErrors)
   30: 
   31:     if (newErrors.length === 0) {
   32:       console.log("Formulario enviado:", values)
   33:     }
   34:   }
   35: 
   36:   return (
   37:     <form onSubmit={handleSubmit}>
   38:       {errors.length > 0 && (
   39:         <div>
   40:           {errors.map((error, index) => (
   41:             <div key={index}>{error}</div>
   42:           ))}
   43:         </div>
   44:       )}
   45: 
   46:       <select name='producto' value={values.producto} onChange={handleChange}>
   47:         <option value="">Selecciona una opción</option>
   48:         <option value="producto a">Producto A</option>
   49:         <option value="producto b">Producto B</option>
   50:       </select>
   51: 
   52:       <input
   53:         type='number'
   54:         name='monto'
   55:         value={values.monto}
   56:         placeholder='Monto de devolución'
   57:         onChange={handleChange}
   58:       />
   59: 
   60:       <textarea
   61:         name='observaciones'
   62:         value={values.observaciones}
   63:         placeholder="Observaciones"
   64:         onChange={handleChange}
   65:       />
   66: 
   67:       <button type="submit">Enviar</button>
   68:     </form>
   69:   )
   70: }
   71: 
   72: export default DevolucionForm


Hay varias cosas que resaltar en este ejemplo:


  1. Usamos un estado interno para almacenar los errores.
  2. Somos responsables por definir el flujo de validación. 

  1. Los dos puntos anteriores pueden ser implementados de diferente manera en cada formulario.

Veamos la propuesta de React Hook Form.


React Hook Form:

    1: import React from "react";
    2: import { useForm } from "react-hook-form";
    3: 
    4: const DevolucionForm = () => {
    5:   const {
    6:     register,
    7:     handleSubmit,
    8:     formState: { errors },
    9:   } = useForm({
   10:     defaultValues: {
   11:       producto: "",
   12:       monto: "",
   13:       observaciones: "",
   14:     },
   15:   });
   16: 
   17:   function onSubmit(data) {
   18:     console.log("Formulario enviado:", data);
   19:   }
   20: 
   21:   return (
   22:     <form onSubmit={handleSubmit(onSubmit)}>
   23:       {Object.keys(errors).length > 0 && (
   24:         <div>
   25:           {errors.producto && <div>El campo 'Producto' es obligatorio</div>}
   26:           {errors.monto && <div>El campo 'Monto' es obligatorio</div>}
   27:         </div>
   28:       )}
   29: 
   30:       <select
   31:         {...register("producto", { required: true })}
   32:       >
   33:         <option value="">Selecciona una opción</option>
   34:         <option value="producto a">Producto A</option>
   35:         <option value="producto b">Producto B</option>
   36:       </select>
   37: 
   38:       <input
   39:         type="number"
   40:         placeholder="Monto de devolución"
   41:         {...register("monto", { required: true })}
   42:       />
   43: 
   44:       <textarea
   45:         placeholder="Observaciones"
   46:         {...register("observaciones")}
   47:       />
   48: 
   49:       <button type="submit">Enviar</button>
   50:     </form>
   51:   );
   52: };
   53: 
   54: export default DevolucionForm;


Analicemos


  1. Usamos la propiedad errors del objeto formState para mostrar si existe algún error en particular.
  2. Las validaciones se setean directamente usando la propiedad register.

NOTA: Entre las características principales de React Hook Form es utilizar los estándares HTML de validaciones. Así se evita reinventar la rueda. Al igual puede implementar validaciones más personalizadas.


¿No estás usando la librería React Hook para crear tus formularios? Revisa los siguientes artículos:


O bien, si quieres subir el nivel y aprender como probar tus formularios usando React Testing Library, te recomiendo mi artículo: Cómo probar formularios en React usando React Hook Form y React Testing Library

Conclusiones

Crear formularios usando React Hook Form es bastante sencillo. Solo debemos usar el hook useForm, iniciar los valores, registrar cada uno de los campos usando el objeto register y pasar una función para invocarse cuando termine de ejecutar el flujo de React Hook Form.

No obstante, es importante identificar cuando utilizar una librería externa para crear nuestros formularios — no en todos los casos aplica. Para identificarlo, hay que tener en cuenta la curva de aprendizaje, el apoyo de la comunidad y los requerimientos del proyecto.


El siguiente paso después de crear un formulario es aprender a probarlo. En mi hub de Testing en React, encontrarás una guía completa para probar tus formularios como un profesional.

Hub de Testing en React