Lecciones del curso
Introducción al desarrollo web full stack con React Router
UI Patterns: Pending & Optimistic
Lo más típico en una aplicación web es mostrar estados de carga cuando el usuario hace submit de algún formulario, o cuando se navega entre rutas. Para este tipo de experiencia típica, React Router nos provee de muchas herramientas que nos permiten reflejar estos estados en nuestra interfaz, adecuadamente. ↻
Veamos entonces, que podemos usar para conseguir esto y al final aprenderemos cómo podemos entregar una experiencia optimista sin estado de carga alguno. Para así, crear la experiencia más rápida y moderna posible. ⚡️
Global pending state o estado de carga global
Podemos usar el hook useNavigation
para un pequeño hack sacándole el atributo location
y convirtiéndolo en un booleano.
import { useNavigation } from "react-router"; export default function Root() { const navigation = useNavigation(); const isNavigating = Boolean(navigation.location); return ( <html> <body> {isNavigating && <GlobalLoadingBar />} <Outlet /> </body> </html> ); }
Si has visto estas barritas hasta arriba de las páginas web, que demuestran que la página está cargando, bueno, a eso me refiero. Podemos usar este hack directamente en el archivo root para que se muestre en todas las rutas. ✅
https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fblog.webjeda.com%2Fassets%2Fthumbs%2Ftop-bar-on-websites-2.jpg&f=1&nofb=1&ipt=1b47deb22aa80be8fd73d437ee06ecc01427d01c37df8580d9f19753366e237d&ipo=images
Estado de navegación local
El indicador de carga podría también usarse de forma local dentro del <NavLink>
; como hemos visto en lecciones anteriores. Si recordamos, traeremos a la memoria que tenemos esta opción en los props className
y style
, además de children
.
import { NavLink } from "react-router"; function Navbar() { return ( <nav> <NavLink to="/"> {({ isPending }) => ( <span>Inicio {isPending && <Spinner />}</span> )} </NavLink> <NavLink to="/blog" style={({ isPending }) => ({ color: isPending ? "gray" : "black", })} > Blog </NavLink> </nav> ); }
Y pues claro, tendrás a la mano los otros dos booleanos isActive
e isTransitioning
.
Estados de carga de un <Form>
Tal vez, el momento más común en el que necesitaremos de estos estados de carga, para comunicar correctamente lo que está pasando en el sitio, sea al momento de hacer submit.
import { useFetcher } from "react-router"; export default function Route() { const fetcher = useFetcher(); return ( <fetcher.Form method="post"> <input type="email" name="email" /> <button type="submit"> {fetcher.state !== "idle"// idle|loading|submiting ? <Spinner /> : "Guardar"} </button> </fetcher.Form> ); }
Aquí vemos un uso declarativo del <Form>
, usando el componente que viene dentro de fetcher
¿para qué? Para poder acceder al atributo state
, claro. Este estado, cambiará, automáticamente, según progrese la comunicación con el servidor. 🥳
Ahora, hay veces que no queremos usar el <fetcher.Form>
que viene dentro del fetcher
, ya sea porque estamos usando nuestro propio componente <form>
o porque, tal vez estemos usando una biblioteca que tiene su propio componente <Form>
como el que está disponible en la librería useForm
.
import { useNavigation, Form } from "react-router"; function NewProjectForm() { const navigation = useNavigation(); return ( <form method="post" action="/subscriptions"> <input type="email" name="email" /> <button type="submit"> {navigation.formAction !== "/subscriptions" // @todo confirm this ? <Spinner /> : "Suscribirme"} </button> </form> ); }
Para estos casos, tenemos otro hook a la mano; useNavigation
incluye un atributo formAction
que nos permite mirar el estado se la sumisión. 🩻 y así, decidir si se está haciendo un submit; pues el valor de formAction
será el pathname que esté haciendo la sumisión.
Aunque, siempre que podamos, mejor usemos el fetcher
. 😉
👀 No confundir
useNavigation
conuseNavigate
, ni tampoco con el hookuseTransition
, que ya está “deprecado”. ‼️
¿Optimistic UI?
Todas las herramientas anteriores nos ayudan a poner spinners, skeletons o loadingBars everywhere. Pero ¡hay una mejor manera! Podemos crear una experiencia mucho más moderna e instantánea hoy en día. Y, React Router es uno de los pocos frameworks realmente preocupados en que los developers full stack logremos transicionar a interfaces optimistas. 🛸
export default function Route({ loaderData }) { const { product } = loaderData; const fetcher = useFetcher(); const isFavorite = fetcher.formData?.has('favorite') ? fetcher.formData.get('favorite') : product.favorite return ( <div> <div>{product.title}</div> <fetcher.Form method="post"> <button name="favorite" value={!isFavorite} > {isFavorite ? <FillStar/> : <EmptyStar/>} </button> </fetcher.Form> </div> ); }
Este es un ejemplo optimistic. Basta con un pequeño ternario para usar el dato que viene de la base de datos o, el dato que se ha enviado al servidor y que el fetcher almacena en sí mismo dentro del formData
. Así, no hay ninguna necesidad de colocar estados de carga. ¡Genial! 😁
No se necesita de mucho más para crear experiencias optimistas, incluso, si te avientas a pensar cada vez más tus interfaces como Optimistic, con todas las herramientas que React Router nos pone a la mano, seguro que con tu creatividad alcanzarás experiencias muy originales y modernas. Para, por fin ¡decirle adios a los spinners! 👋
Abrazo. Bliss. 🤓
///
Dolores
Desde los quince años había decidido no tener hijos, para así contribuir a la causa y dedicar su vida, sin interrupción ni distracciones, a beneficiar a su familia por completo. Así le había enseñado Padre. Y ella había cumplido con su deber cabalmente, sin reproche o rebeldías, nada. Se había sometido por completo a la visión familiar y había aportado siempre más del 90% de sus ingresos en cualquier empleo o emprendimiento que su buena educación le permitía conseguir y realizar. Siempre frugal, vestía vestidos viejos que remendaba con parches coloridos. Pero claro, un día pasó, le pasó por la cabeza ese agradable y a la vez desagradable pensamiento que desembocó en ese sentimiento extraño. No supo nunca bien si ese sentimiento extraño le gustaba o le enfadaba, tal vez le angustiaba, seguro le angustiaba porque de inmediato y de forma muy predecible: a sus cuarenta, ella comenzó a pensar en lo bonito que sería tener un bebé. Ese día, ella sintió como si alguien hubiese tosido dentro del teatro; entre la audiencia; revelando la farsa; de pronto tuvo unas ganas incontrolables por salirse de la obra, dejar el personaje, arrojar el ropaje, y correr, correr lejos de esas plays correr directo en busca de una verga y así lograr embarazarse lo antes posible. Pero, solo se atrevía a pensarlo. Pensó también en hablarlo con su padre y su madre, más con su padre que con su madre, pero hablarlo con ellos. Luego se arrepintió, pues imaginó más de diez maneras en las que Padre reaccionaría, o su madre y, en ninguna de sus imaginaciones el asunto resultaba en abrazos y sonrisas. Así que simplemente decidió huir. Planeo ahorrar por un año y luego comprar un ticket de bus rumbo a Tlaxcala. Le gustaba pensar que históricamente los tlaxcaltecas, siempre habían tenido mejor vista desde este punto del país. No era lejos, un par de horas de viaje, ahí encontraría un buen hombre, o mal hombre, eso no importaba mucho, importaba más otro rasgo. Pero, esto también se lo inventó en su imaginación, ni siquiera apartó dinero para su ahorro ni una sola vez. Su inconsciente no la dejó, yo digo. Ahí es donde ya vivían profundas e inflamadas las estacas sangrientas y punzantes que su padre colocó en su corazón. Ella solo siguió, como Padre lo había encomendado a ella y a todas sus hermanas: no tuvo hijos, ni novios, ni planes independientes, ni más del diez por ciento de su salario que percibía en sus empleos que mantuvo desde los 15 años, que Padre lograba conseguirles trabajo legalmente firmando permisos, hasta los 54, que Dolores sufrió un infarto agudo del miocardio que la arrasó. Aunque con la muerte, se liberó mucho antes que sus otras ocho hermanas. Se dice que, don Felicitas las enterró a todas. Que, era un hombre muy saludable y que gastaba mucho. Y, aunque vivió hasta los 120 años, los últimos cuarenta los vivió en extrema pobreza, enfermedad y soledad; porque se le murió su mujer Eduviges. Se le acabaron las hijas y luego se le acabó el dinero y luego todo.