Escuela Frontend
React

Formularios en React: Una Guía Práctica

Mateo García
Autor
Mateo García

El uso de formularios en desarrollo web es fundamental a la hora de crear aplicaciones interactivas ya que con ellos podemos validar y enviar información a nuestros servidores. La verdad es que trabajar con formularios puede ser más desafiante de lo que pudiéramos creer.

Para crear un buen formulario, debemos tener en cuenta los siguientes aspectos:

  • Accesibilidad: Millones de usuarios en el mundo sufren algún tipo de discapacidad y navegan los sitios web a través de herramientas diferentes al mouse y el teclado, por lo tanto, debemos tener en cuenta la semántica de los elementos HTML que usemos para crear el formulario, además no será suficiente usar las estrategias de validación convencionales propuestas por los navegadores.
  • Validación: Cada campo que existe en el formulario puede tener unas reglas particulares. Unos campos pueden ser opcionales, otros obligatorios, también permiten ingresar correos electrónicos, pueden requieren valores mínimos o máximos, entre otros. Comunicar a todos los usuarios acerca de los valores permitidos en un campo específico es una función fundamental de las validaciones de campos.
  • Serialización: Cuando un usuario ha terminado de diligenciar el formulario, su información se encuentra en algún espacio de memoria en el que usa la aplicación. Obtener esa información, manipularla y enviarla adecuadamente puede ser un reto en algunas ocasiones.

Objetivo

Una vez que termines de leer este artículo vas a entender las características de la creación de formularios web, serás capaz de implementar tus propios formularios, aprenderás acerca de patrones de diseño populares en React y, conocerás algunas de las bibliotecas más usadas para solucionar los retos mencionados anteriormente.

Outline

¿Cómo no aprender este tema puede impactar el trabajo (corto plazo) y la carrera (largo plazo) de un desarrollador?

No utilizar React adecuadamente para manipular formularios puede llevarnos a construir aplicaciones con problemas de testing, mantenibilidad, e incluso rendimiento.

¿Cómo este tema se relaciona con el panorama general del desarrollo web?

Los formularios son elementos fundamentales del ecosistema web, gracias a ellos almacenamos información ingresada por el usuario. Además, cumplen diferentes roles en un sitio o aplicación web, hacen parte del proceso de registro y autenticación de usuarios, también son usados para la creación, modificación y lectura de datos.

¿A quién está dirigido este artículo?

Este artículo está diseñado para personas que usan React en su día a día, que buscan escribir mejores formularios y, que quieran implementar soluciones con patrones de diseño modernos y usar bibliotecas populares que solucionan estos mismos problemas.

¿Que encontraras en este articulo?

  1. Patrones de React para crear formularios
    1. Componentes Controlados
    2. Componentes No Controlados
  2. Validación de formularios
    1. Validaciones síncronas
    2. Validaciones asíncronas
  3. Ecosistema de bibliotecas de formularios
    1. Probando Formik
    2. Probando React Hook Form
    3. Probando React Final Form
  4. Comentarios sobre accesibilidad en formularios
  5. Conclusiones

Patrones de React para crear formularios

Componentes Controlados

Un componente controlado es aquel que usa los cambios de estado o cambios de props como fuente de verdad para representarse en el DOM.

De manera más concreta, es un componente que mantiene una sincronización entre el estado de React y el valor del campo, si el estado cambia, el valor cambia.

Puedes pensar en el cómo un proceso cíclico:

Relación entre un componente, su cambio de estado y el estado en sí mismo

https://res.cloudinary.com/escuela-frontend/image/upload/v1629783470/articles/formularios-en-react-una-guia-practica/controlled_component_yezpul.png

A continuación, tenemos nuestro primer componente controlado:

import React from "react";
function Form() {
const [values, setValues] = React.useState({
email: "",
password: "",
});
function handleSubmit(evt) {
/*
Previene el comportamiento default de los
formularios el cual recarga el sitio
*/
evt.preventDefault();
// Aquí puedes usar values para enviar la información
}
function handleChange(evt) {
/*
evt.target es el elemento que ejecuto el evento
name identifica el input y value describe el valor actual
*/
const { target } = evt;
const { name, value } = target;
/*
Este snippet:
1. Clona el estado actual
2. Reemplaza solo el valor del
input que ejecutó el evento
*/
const newValues = {
...values,
[name]: value,
};
// Sincroniza el estado de nuevo
setValues(newValues);
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
value={values.email}
onChange={handleChange}
/>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
value={values.password}
onChange={handleChange}
/>
<button type="submit">Sign Up</button>
</form>
);
}

Si imprimimos la variable values verás que el comportamiento del componente es actualizarse con estos valores cada que alguno de los campos cambia:

Cambio del estado al editar el campo de correo o contraseña

https://res.cloudinary.com/escuela-frontend/image/upload/v1629783474/articles/formularios-en-react-una-guia-practica/controlled_component_log_demi0q.gif

Componentes No Controlados

Un componente no controlado, es aquel que no usa el estado o las props para representarse en el DOM, y, por el contrario, usa la API del DOM. La manera en la que React obtiene los valores es usando la API de REF.

Relación entre un componente, la API de REF y la API del DOM.

https://res.cloudinary.com/escuela-frontend/image/upload/v1629783470/articles/formularios-en-react-una-guia-practica/uncontrolled_component_bzi6ah.png

Los formularios en React son buenos casos de uso para aplicar el patrón de componentes no controlados.

import React from "react";
function Form() {
const formRef = React.useRef();
function handleSubmit(evt) {
evt.preventDefault();
/*
1. Usamos FormData para obtener la información
2. FormData requiere la referencia del DOM,
gracias al REF API podemos pasar esa referencia
3. Finalmente obtenemos los datos serializados
*/
const formData = new FormData(formRef.current);
const values = Object.fromEntries(formData);
// Aquí puedes usar values para enviar la información
}
return (
<form onSubmit={handleSubmit} ref={formRef}>
<label htmlFor="email">Email</label>
<input id="email" name="email" type="email" />
<label htmlFor="password">Password</label>
<input id="password" name="password" type="password" />
<button type="submit">Submit</button>
</form>
);
}

Es importante mencionar que:

  1. Este componente no hace re-render por sí mismo, ya que no tiene cambios de estado.
  2. Los valores de cada campo los gestiona la API del DOM, por lo tanto, React solo hace render y obtiene las referencias de los inputs usando la API de REF.

Validación de Formularios

Las validaciones en los formularios buscan guiar y comunicar al usuario sobre los valores adecuados que cada uno de los campos espera almacenar. Los casos de uso son numerosos, a veces se busca que tenga un rango especifico de caracteres, otras veces queremos que cumpla un patrón de texto preciso, o quizás queremos que responda frente a un campo previamente ingresado.

Para calcular si los valores del campo son correctos o incorrectos podemos ejecutar una serie de evaluaciones en nuestra base de código que pueden ocurrir de manera síncrona o asíncrona, miremos de que se trata:

Validaciones Síncronas

Una validación síncrona es aquella que evalúa el estado del campo en el hilo principal de Javascript. La mayoría de las validaciones son de este tipo, y los casos de uso más normales son validaciones de correos electrónicos, nivel de seguridad de contraseña, y para generalizar, todo aquello que sea posible usando Regexp.

A continuación, estaremos implementando un formulario usando una validación síncrona.

import React from "react";
const emailRegexp = new RegExp(/[^@ \t\r\n]+@[^@ \t\r\n]+\.[^@ \t\r\n]+/);
function Form() {
const [emailField, setEmailField] = React.useState({
value: "",
hasError: false,
});
function handleChange(evt) {
/*
Esta función es la misma usada
en la sección de componentes de
Componentes Controlados
*/
}
function handleBlur() {
/*
1. Evaluamos de manera síncrona
si el valor del campo no es un correo valido.
2. Recordar que este método se llama
cada vez que abandonamos el campo y evita
que el usuario reciba un error sin haber terminado
de poner el valor.
*/
const hasError = !emailRegexp.test(emailField.value);
setEmailField((prevState) => ({ ...prevState, hasError }));
}
return (
<form>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
value={emailField.value}
onChange={handleChange}
{/* onChange para sincronizar el valor del campo */}
onBlur={handleBlur}
{/* onBlur para sincronizar la validación del campo */}
aria-errormessage="emailErrorID"
aria-invalid={emailField.hasError}
/>
{/*
1. Solo muestra el mensaje de error cuando hasError sea true
2. Crea una relación lógica entre el campo y el mensaje de error,
favoreciendo la semántica y la accesibilidad del campo.
*/}
<p
id="msgID"
aria-live="assertive"
style={{ visibility: emailField.hasError ? "visible" : "hidden" }}
>
Please enter a valid email
</p>
</form>
);
}

A continuación, verás a nuestro componente validar el correo electrónico que estamos ingresando, y una vez el error deba comunicarse (onBlur) lo verás aparecer en acción.

Ejemplo de validación síncrona de un campo de correo electrónico

https://res.cloudinary.com/escuela-frontend/image/upload/v1629783470/articles/formularios-en-react-una-guia-practica/sync_validation_ob0swt.gif

Validaciones Asíncronas

Las validaciones asíncronas son aquellas que determinen el estado del campo usando algún servicio externo que bloquea el hilo principal de Javascript.

Suelen ser usados para comparar valores que ingresa el usuario contra una base de datos, verificar si una dirección es válida, si un correo no está en uso, si un nombre de usuario ya ha sido registrado, entre otros.

A continuación, estaremos implementando un formulario usando una validación asíncrona.

import React from "react";
function getEmailAvailability(email) {
/*
Imaginemos que tenemos un servicio que valida
si el correo enviado está disponible o no
*/
}
function Form() {
const [emailField, setEmailField] = React.useState({
value: "",
hasError: false,
});
function handleChange(evt) {
/*
Esta función es la misma usada
en la sección de componentes de
Componentes Controlados
*/
}
async function handleBlur() {
/*
Llamamos al servicio y
definimos si hay o no error
*/
const hasError = await getEmailAvailability(emailField.value);
setEmailField((prevState) => ({ ...prevState, hasError }));
}
return (
<form>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
value={emailField.value}
onChange={handleChange}
onBlur={handleBlur}
aria-errormessage="emailErrorID"
aria-invalid={emailField.hasError}
/>
<p
id="msgID"
aria-live="assertive"
style={{ visibility: emailField.hasError ? "visible" : "hidden" }}
>
This email is already registered
</p>
</form>
);
}

En este ejemplo dos cosas cambiaron:

  1. El tipo de evaluación que hacemos es asíncrono, en este caso tenemos estamos haciendo un llamado a un servicio que, para este ejemplo estamos simulando.
  2. handleValidation pasa a ser una función asíncrona. Luego de que el llamado al servicio concluya ocurrirá un cambio de estado con la respuesta del servidor (correo disponible o no).

Veamos la validación en acción:

Ejemplo de validación asíncrona de un campo de correo electrónico

https://res.cloudinary.com/escuela-frontend/image/upload/v1629783470/articles/formularios-en-react-una-guia-practica/async_validation_qgx3sm.gif

Ecosistema de bibliotecas de formularios

El uso de bibliotecas y frameworks es parte del día a día de un desarrollador frontend, el ecosistema de Javascript es bastante amplio y, gracias a ello una alta cantidad de soluciones ya han sido abstraídas y puestas a disposición de cualquier proyecto open source o privado.

El manejo de formularios en React no es la excepción, a continuación, te propongo que exploremos algunas de las bibliotecas más populares para el manejo de formularios en React.

En cada uno de los paquetes que veremos a continuación, analizaremos algunos aspectos fundamentales como:

Formik

Formik es una biblioteca exclusiva para el manejo de formulario en React y React Native. Se encarga de las abstracciones más comunes, es intuitiva, y finalmente es adoptable por su simplicidad y tamaño.

Su peso minificado y en GZIP es de: 13 kB

Está biblioteca ofrece dos modos de uso, la primera es usando un Provider llamado Formik y tiene algunas ventajas como poder usar los componentes que Formik ha abstraído como <Field />, <ErrorMessage />, entre otros.

La segunda opción es más minimalista y se usa través de la API de Hooks usando uno de ellos llamado useFormik.

A continuación, usaremos useFormik para construir un componente de formulario básico:

import React from "react";
import { useFormik } from "formik";
function Formik() {
const { handleSubmit, handleChange, values } = useFormik({
initialValues: {
email: "",
password: "",
},
onSubmit: async function (values) {
// Aquí puedes usar values para enviar la información
},
});
return (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<input
id="email"
name="email"
type="email"
onChange={handleChange}
value={values.email}
/>
<label htmlFor="password">Password</label>
<input
id="password"
name="password"
type="password"
onChange={handleChange}
value={values.password}
/>
<button type="submit">Sign Up</button>
</form>
);
}

useFormik tiene una gran variedad de parámetros que podemos suministrar para controlar el formulario de manera flexible.

En el snippet anterior:

  1. Importamos y ejecutamos dentro del componente el método useFormik, definiéndole el estado inicial y la función que debe ejecutarse cuando se guarde el formulario.
  2. useFormik nos devuelve un objeto con diferentes métodos y atributos que definen el estado del formulario.
  3. handleSubmit contiene la lógica que ejecutara el formulario al guardarse
  4. handleChange sincroniza el valor de los campos con el estado usando componentes controlados
  5. values contiene los valores actuales del formulario
  6. Le pasamos al formulario y los campos los métodos y valores descritos anteriormente. De esta manera el estado estará disponible para ser usado cada que el componente lo requiera.
  7. Cuando el evento de submit ocurre, la función onSubmit tiene los valores disponibles para ser usados.

React Hook Form

React Hook Form es una biblioteca construida sobre la API de React Hooks, está enfocada en el perfomance, la integración con otras bibliotecas de UI como react-datepicker, downshift, chakra, entre otros.

Su peso minificado y en GZIP es de: 8.6 kB

Todo la API de react-hook-form como lo dice su nombre, está basado en hooks. Su hook principal es useForm.

A continuación, verás la implementación básica de un formulario de registro usando esta biblioteca.

import React from "react";
import { useForm } from "react-hook-form";
function HookForm() {
const { register, handleSubmit } = useForm();
function onSubmit(values) {
// Aquí puedes usar values para enviar la información
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<label htmlFor="email">Email</label>
<input type="email" {...register("email")} />
<label htmlFor="password">Password</label>
<input type="password" {...register("password")} />
<button type="submit">Sign Up</button>
</form>
);
}

En el snippet anterior:

  1. Importamos y ejecutamos dentro del componente el método useForm.
  2. useForm nos retorna dos métodos, register y handleSubmit.
  3. register se usa en cada uno de los campos, está es la manera como se sincroniza el estado con el formulario.
  4. handleSubmit se usa para especificar el método que debe de ejecutarse cuando el formulario es guardado.
  5. Cada input usa register describiendo el identificador del campo.
  6. Cuando el evento de submit ocurre, la función onSubmit tiene los valores disponibles para ser usados.

React Final Form

React Final Form es un wrapper de Final Form, una biblioteca agnóstica a frameworks que sirve para crear formularios en Javascript. Su implementación esta basada en el patrón de observador y de esta forma hace re-render solo de los componentes que cambiaron.

Su peso minificado y en GZIP es de: 13.9 kB (Requiere instalar final-form)

Como veremos a continuación React Hook Form funciona a través de Context API usando un componente <Form /> que a través de render props nos retorna el estado actual del formulario y así, validar y manipular los campos a nuestras necesidades.

import React from "react";
import { Form, Field } from "react-final-form";
function FinalForm() {
function onSubmit(values) {
// Aquí puedes usar values para enviar la información
}
return (
<Form onSubmit={onSubmit}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<label htmlFor="email">Email</label>
<Field name="email" component="input" id="email" type="email" />
<label htmlFor="password">Password</label>
<Field
name="password"
component="input"
id="password"
type="password"
/>
<button type="submit">Sign Up</button>
</form>
)}
</Form>
);
}

En el snippet anterior:

  1. Importamos y utilizamos el componente <Form /> suministrando la función que se ejecutará cuando el componente sea guardado.
  2. El contenido interno del componente <Form /> es un render prop, o una función que proporciona propiedades y/o estado interno.
  3. En nuestro ejemplo solo estamos usando el método handleSubmit que nos proporciona el render prop, sin embargo, en los parámetros de esa función podremos obtener información del estado del formulario, como por ejemplo, si está activo, si ya está siendo diligenciado, si tiene errores, etc.
  4. Finalmente, React Final Form nos proporciona un componente <Field /> que se sincroniza internamente con el contexto del componente <Form />, este componente representado el <input /> usado en los ejemplos anteriores, sin embargo, tiene una API bastante flexible que permite integrar componentes existentes usando input, select, o incluso textarea.

Comentarios sobre accesibilidad en formularios

  1. Para mejorar la experiencia de la navegación en el formulario puedes crear una relación lógica entre el elemento <label /> y el elemento <input /> a través de los atributos for (htmlFor en React) e id respectivamente. Esto permite que el <input /> haga focus cuando se seleccione el <label />, esta técnica ayuda a las personas con discapacidades motoras a seleccionar el campo fácilmente, especialmente cuando usamos checkbox o radio button.
  2. En los ejemplos que vimos en código no le especificamos a los inputs la propiedad aria-label, ya que con la estrategia mencionada en el punto 1, el input usa como label el contenido de la etiqueta <label /> a la cual se relacionó usando el atributo for o htmlFor. Debes usar aria-label si por alguna razón tu <input /> no tiene un <label /> asociado, de lo contrario no será fácil de interpretar para una persona con discapacidades visuales.
  3. La accesibilidad también se trata de escribir estilos adecuadamente, es por ello que nunca debes de usar outline: none. El outline es ese contorno amarillo que se visualiza cuando un campo hace focus, y es usado para orientar a los usuarios que no usan el mouse sobre donde están ubicados en tu sitio.
  4. Si vas a indicarle alertas y/o errores a los usuarios de tu formulario es importante que no dependas solo de colores (generalmente amarillo y rojo respectivamente). Muchos usuarios con problemas de visión se apoyan en configuraciones que cambian el contraste de color en el navegador haciendo obsoleta la comunicación por colores, puedes compensarlo escribiendo buenos mensajes de alerta y error.

Conclusiones

El desarrollo web habilita la interactividad de los usuarios a través de HTML y Javascript, los formularios son un ejemplo de la capacidad que tiene el navegador para comunicarse con los usuarios de un sitio o aplicación. React es una biblioteca que facilita la creación de interfaces visuales entre ellas formularios, sin embargo, es fundamental tener buenas prácticas para garantizar una buena experiencia de usuario y capacidad de mantenimiento dentro de un equipo de desarrollo, es por ello que, hay que tener en consideración todo el público que puede usar nuestras aplicaciones. Todas las aplicaciones deberían ser fundamentadas sobre principios de accesibilidad, y no pensar en ellos como un feature al final del backlog del proyecto.

Espero que esta guía te haya sido de utilidad y te animo a que sigas aprendiendo acerca de React, formularios web y como hacer de la web un lugar equitativo para todos.

Artículos Relacionados

¿Quieres mejorar tus habilidades de frontend?