Cómo crear formularios complejos con React Hook Form

Interconecta tus inputs rápidamente con useFormContext

Fernando Albertengo
Crehana
Published in
6 min readNov 10, 2022

--

Primero que nada, volvimos! 🎉🎉🎉

Pasaron un montón de cosas desde el 15 de mayo de 2018, el día de la última publicación del Crehana Tech Blog.

Si tienes curiosidad, en términos de cambios recopile unos 101.242 commits (sin contar merges) realizados por aproximadamente 280 devs solo en los proyectos que tengo acceso 🤯
Por si fuera poco, no solo registramos 4.000.000 de estudiantes nuevos, sino que en un nuevo enfoque a un negocio más B2B, en ese periodo se sumaron más de mil organizaciones! 🚀

No sin antes dedicarles un aplauso a todos los Crehaners por semejantes logros monumentales, paso a nuestro tema del día.

👏🏻👏🏼👏🏽👏🏾👏🏿

🧾 Formularios en React

Nuestros Frontend Devs saben que Crehana está bellamente dibujado utilizando React + Typescript en conjunto a una batería de librerías cuidadosamente creadas y curadas por nosotros mismos.

Dicha tecnología esta basada en el manejo de estado de elementos que componen a nuestras páginas, nuestros componentes.
Dependiendo del estilo de código adoptado, un formulario puede modificar el estado de docenas de inputs de distintos tipos, con validaciones y en algunos casos, distribuidos a lo largo y ancho de toda una jerarquía de componentes compleja.

🔎 Caso de estudio

Supongamos un requerimiento especifico:

Se debe implementar un formulario de carga de imágenes, se deben reutilizar inputs preexistentes y el formulario debe contar con:
- Obtención y precarga de valores previos.
- Validación de errores comunes.
- Control de formato y tamaño de archivo, que varían dependiendo del input esperado.

Y una “solución”:

Se puso un poco larga la solución no? 😅

Si bien el código es bastante sencillo, cabe aclarar que en el caso original se necesitaban seis inputs, algunos tanto dependientes entre sí como de valores externos al form.

Un formulario puede tener docenas de inputs y con una aplicación como la de arriba, el código a mantener puede resultar grande y feo 👹

Así damos cuenta que mantener un buen control de la información de nuestro formulario puede convertirse rápidamente en una tarea pesadillesca.

Pues no más, introduciendo:

Formularios performantes, extensibles y con validacion facil de usar, que tal?

✨ useForm, el núcleo de todo

useForm is a custom hook for managing forms with ease. It takes one object as optional argument.

En su documentación podemos encontrar el siguiente ejemplo:

Sin ir demasiado lejos, vemos un hook listo para usar que nos suple métodos para controlar y manipular un formulario.
Al instanciarlo debemos registrar los campos tipados, y opcionalmente también valores por default, validadores customizables, criterios de validación, un contexto (!) y demás.
TL;DR: Adiós a esos manejos de estado super extensos! 🚀

Veamos una implementación basica:

Presta especial atención al registrado del input 🔍

Para nosotros useForm va a representar el núcleo funcional de nuestra página, donde el formulario se va a instanciar, configurar, emitir, y no va a estar en nuestra página! 😮

Ahora sí. Ya puedo presentar a nuestro verdadero campeón del día:

🦸 useFormContext, el form por wifi

This custom hook allows you to access the form context. useFormContext is intended to be used in deeply nested structures, where it would become inconvenient to pass the context as a prop.

Luego de revisar la documentación y en pocas palabras, este bello hook nos permite acceder los métodos de un formulario desde cualquier parte! Adiós a esos feos prop-drillings! 🚀

El único requerimiento para que todo salga sobre ruedas, es envolver todos los componentes que tengan que acceder al formulario con un proveedor.

Veamos un ejemplo básico de uso:

Accesando el form desde hijos anidados 🙌

El poder que acabamos de obtener es enorme. No solo reducimos la complejidad del código al reducir el manejo de estados, sino que con muy pocos retoques podemos abstraer toda la lógica del formulario de la jerarquía de componentes que conforme nuestra pagina.

De esta manera, durante el desarrollo, solo tenemos que preocuparnos de tres pasos principales, claramente definidos:

  1. Crear instanciar e inicializar nuestro formulario correctamente, registrando nuestros inputs.
  2. Proveer el contexto del form desde la raíz común a todos los componentes del formulario.
  3. Consumir los métodos del formulario desde nuestros inputs.

Veamos cada punto en detalle en la siguiente sección.

🚣 Embelleciendo nuestro Form

Si revisamos nuestro caso de estudio una vez más y lo extrapolamos a los tres items que deducimos hace momentos, podremos hacer nuestro formulario mucho mas robusto y limpio:

1. El Formulario, hecho custom hook

Vamos a abstraer toda la lógica a un hook propio con una clara responsabilidad: Instanciar y proveer nuestro formulario.

Estuvo facil de leer, verdad? 😌

No es posible hacer demasiado énfasis en esto: No olvidemos inicializar nuestros campos con el tipo que finalmente el input va a registrar. Sin el tipo correcto el input no podrá hacer su trabajo! ⚠️

En segunda instancia, no olvidemos registrar nuestros campos al form! 😆 Esto es posible en cualquier punto entre la instanciación del formulario y la creación del input. Nosotros vamos a hacerlo aquí mismo por dos motivos:

  • Queremos centralizar tanta lógica como sea posible aquí, ya que queremos establecer una responsabilidad clara.
  • A diferencia de los ejemplos anteriores, queremos hacer uso de Custom Inputs, por lo que nos valdremos de los métodos register y setValue para actualizar el form.

Para saber mas sobre register, lee esto! También recomiendo leer esto.

2. La raíz

Podremos integrar nuestro hook de la siguiente manera:

Cuanta limpieza 🧹

Aquí estamos tomando los métodos y proveyéndolos a todos los hijos del form. Además, estamos agregando un componente FormSubmit con la responsabilidad de decirnos si podemos darle al botón submit, y en dicho caso, habilitarlo 🚀

También podemos ver que podemos acceder a los valores del form directamente desde este punto por medio del método getValues().

A modo de ejemplo, habilitaremos mas campos dependiendo de un booleano que debería estar inicializado y registrado apropiadamente en nuestro useImagesForm, permitiéndonos controlar dependencias entre campos.

2. Los inputs

Ya casi lo tenemos! Solo nos queda un último paso, integrar nuestro form a los custom inputs:

Ignoremos la magia ✨ que este realizando CustomImageInput por un momento y concentremonos en lo interesante, los metodos que estamos accediendo gracias a useFormContext.

🥸 Watch

Al invocar esta función:
- Estamos obteniendo un valor por default, porque ya inicializamos el form con el campo images y lo registramos.
- Observamos los cambios en tiempo real al formulario usando la función watch y pasándole el name del campo que registramos. Ver más.

✍️ setValue

Nos permite setear dinámicamente el valor de un campo registrado seleccionándolo por su name, dándonos además opciones adicionales.

💀 errors

Aquí podemos acceder los errores de nuestro form por medio de la convención errors.[name].message

Una nota en validaciones; dependiendo de como hayas implementado tu form, puede que además necesites implementar un useEffect que observe los cambios de la variable entregada por watch, y ejecutar una revalidación con trigger, por ejemplo:

Esto tiene mucho que ver con el como interactúa tu custom input con el form.

🏁 Conclusiones

Hemos creado un hook que nos permite centralizar prácticamente toda la lógica de un formulario, customizarla, y proveerla a tantos campos como queramos registrar.

Entre otras motivaciones, esos métodos que proveemos están cuidadosamente curados para mitigar la necesidad de manejar estados en nuestros inputs, reduciendo la dificultad de mantener nuestro código.

Asimismo, llegamos a ser un poco más CLEAN a la hora de definir y distribuir responsabilidades. No más componentes monolíticos para manejar unos pocos campos de un form!🚀

Nota del Autor

Gracias por llegar hasta aquí! Este fue mi primer artículo desde siempre así que significa mucho, espero que te haya gustado y servido. 🙌

No dudes en dejar tu opinión en la sección de comentarios!

Hasta la próxima! 😄

--

--

Fernando Albertengo
Crehana
Editor for

React Developer | Sr Frontend Developer at @crehanacom | Always Learning | WH40K Fan