Optimizando el manejo de formularios con useTransition en lugar de useState
Por qué reemplazar booleanos isLoading con useTransition mejora el rendimiento percibido, mantiene la UI responsiva durante envíos asíncronos y simplifica el manejo de estado en apps React.
Ender Puentes
En el desarrollo con React, manejar estados de carga con valores booleanos como [isLoading, setIsLoading] es un patrón común, especialmente en formularios y otras acciones asíncronas. Aunque funciona, a medida que las aplicaciones crecen en complejidad, usar useState puede impactar negativamente el rendimiento y la experiencia de usuario. Afortunadamente, React ofrece herramientas como useTransition para manejar transiciones de UI de forma más eficiente.
El enfoque típico con useState
Este es un ejemplo común de manejo de estado de carga en un formulario usando useState:
import { useState, FormEvent } from "react";
function ContactForm() {
const [isLoading, setIsLoading] = useState<boolean>(false);
const handleSubmit = async (event: FormEvent) => {
event.preventDefault();
setIsLoading(true);
// Simulate a form submission
await fakeApiRequest();
setIsLoading(false);
alert("Form submitted successfully");
};
return (
<form onSubmit={handleSubmit}>
<input type="text" placeholder="Your name" required />
<button type="submit" disabled={isLoading}>
{isLoading ? "Sending..." : "Submit"}
</button>
</form>
);
}
function fakeApiRequest(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, 2000));
}
export default ContactForm;En este ejemplo, usamos useState para deshabilitar el botón de envío mientras esperamos la respuesta de la "API". Aunque funciona, no es ideal cuando el rendimiento y respuestas rápidas de UI son críticos.
Problemas con useState
Problemas de rendimiento
Al usar useState para controlar un estado de carga como isLoading, cada vez que actualizamos ese estado, React re-renderiza el componente. Esto está bien en casos simples, pero actualizaciones frecuentes o lógica más compleja pueden generar problemas de rendimiento. Cambiar un booleano como isLoading puede hacer que la UI se congele momentáneamente, impidiendo la interacción del usuario hasta que el estado cambie nuevamente.
Esto ocurre porque React dispara un re-render cuando detecta un cambio de estado con useState. En aplicaciones grandes con múltiples componentes, esto puede volverse innecesario o costoso, afectando la velocidad y la percepción del usuario, haciendo que la app se sienta lenta o poco responsiva.
Problemas de sincronización de estado
Además del rendimiento, useState puede causar problemas de sincronización, especialmente cuando múltiples componentes o hooks dependen del mismo estado. Si no se maneja correctamente, la UI puede mostrar un estado inconsistente, particularmente con eventos asíncronos o cambios de estado rápidos.
Gestionar actualizaciones de estado manualmente se vuelve más desafiante a medida que la app crece, llevando a un código más complejo con potenciales errores de sincronización. Aquí es donde useTransition es valioso para controlar transiciones sin bloquear la UI.
¿Qué es useTransition?
useTransition es un hook que permite marcar una actualización de estado como "transicional", lo que significa que React puede priorizar actualizaciones más urgentes (como animaciones o interacciones del usuario) y manejar las transiciones cuando sea más conveniente.
La ventaja clave de useTransition es que mejora el rendimiento percibido de la app, permitiendo que React gestione las actualizaciones de forma más inteligente, ofreciendo transiciones más suaves y mejores experiencias de usuario.
Beneficios de usar useTransition
1. Mejor rendimiento percibido: useTransition permite a React retrasar actualizaciones no urgentes, mejorando la sensación de velocidad de la app.
2. Transiciones más suaves: En lugar de bloquear la UI inmediatamente (como con useState para isLoading), useTransition permite que los usuarios sigan interactuando mientras la operación en segundo plano se completa.
3. Menos código y más declarativo: Con useTransition evitamos escribir lógica adicional para manejar el estado de carga. React marca automáticamente las actualizaciones como transicionales.
4. Flexibilidad en apps grandes: Si tu app tiene múltiples operaciones asíncronas que podrían afectar el rendimiento, useTransition ayuda a controlar esas transiciones mejor, asegurando que la UI no se vea impactada por operaciones no urgentes.
Reemplazando useState con useTransition
Veamos cómo se vería nuestro formulario usando useTransition para manejar el estado de "carga". En lugar de bloquear la UI con setIsLoading(true), haremos que el envío del formulario sea una transición suave que no interrumpa las interacciones del usuario.
import { useState, useTransition, FormEvent, ChangeEvent } from 'react';
interface FormData {
name: string;
}
function ContactForm() {
const [isPending, startTransition] = useTransition();
const [formData, setFormData] = useState<FormData>({ name: '' });
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
startTransition(async () => {
// Simulate a form submission
await fakeApiRequest();
alert("Form submitted successfully");
});
};
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
value={formData.name}
onChange={handleInputChange}
placeholder="Your name"
required
/>
<button type="submit" disabled={isPending}>
{isPending ? "Sending..." : "Submit"}
</button>
</form>
);
}
function fakeApiRequest(): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, 2000));
}
export default ContactForm;En este nuevo ejemplo, eliminamos por completo la necesidad de manejar manualmente un estado isLoading. En su lugar, usamos el booleano isPending que provee useTransition para saber si estamos en medio de una transición. El código es más limpio y dejamos que React gestione cuándo y cómo actualizar el estado del componente de forma más eficiente.
Conclusión
Cambiar de useState a useTransition para manejar estados como isLoading puede parecer un ajuste menor, pero en aplicaciones grandes este enfoque puede marcar una diferencia significativa en rendimiento y experiencia de usuario. El manejo de transiciones se vuelve mucho más fluido y React optimiza las actualizaciones de UI de forma inteligente.