cover

¿Cómo usar fetcher?


Si tú ya te actualizaste a React Router v7 y ya lo estás usando como framework, entonces este video te va a caer de perlitas. 🧋

Te voy a dar un ejemplo de cómo puedes usar fetcher para todos tus consumos de datos, apuntando directo a tus end-points o rutas de recursos. ✅

Vamos a crear un pequeño componente de suscripción

Cómo no, un caso de uso simple y real. Para poder añadir a alguien a nuestra lista de correos. 📧

const Subscription = ({ asset, actionId, }: { actionId?: string; asset: Asset; }) => { const fetcher = useFetcher(); const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = new FormData(e.currentTarget); formData.set("intent", "free_subscription"); formData.set("assetId", asset.id); formData.set("actionId", actionId); fetcher.submit(formData, { method: "post", action: "/api/v1/user", }); }; const isLoading = fetcher.state !== "idle"; if (fetcher.data?.success) { return ( <section className="flex flex-col justify-center items-center text-xl h-[100px] pt-12"> <h2>¡Gracias por suscribirte!</h2> <br /> <p>Ahora revisa tu correo. 📬</p> <EmojiConfetti /> </section> ); } return ( // hidden on mobile <fetcher.Form onSubmit={handleSubmit} className="hidden md:block"> <Input placeholder="Escribe tu nombre" name="displayName" inputClassName="border-0 border-b-2 rounded-none" /> <Input required placeholder="Escribe tu email" name="email" className="min-h-full m-0" inputClassName="border-0 border-b-2 rounded-none" /> <button disabled={isLoading} type="submit" className={cn( "hidden md:grid h-16 w-full text-2xl font-bold border-b-[2px] bg-[#CE95F9] border-black place-content-center disabled:text-gray-500 disabled:bg-gray-400/40" )} > {asset.template?.ctaText ? asset.template.ctaText : asset.price <= 0 ? "Suscribirse gratis" : "Comprar"} </button> </fetcher.Form> ); };

Este es un ejemplo con un componente autónomo. Este componente es reutilizable, puede participar en cualquier layout a cualquier nivel, pues su comunicación con el servidor está fijada en el atributo action del fetcher.submit.

A este tipo de componentes Kent C. Dodds los llamó full stack components.

Respondamos tres preguntas:

¿Cómo enviamos data?

¿Cómo mostramos la carga?

¿Cómo recibimos la respuesta?

Comencemos, pues, con la primera pregunta:

¿Cómo enviamos data?

Podemos responder al clic de cualquier botón, pero lo más regular es manipular el evento onSubmit de una etiqueta <form>.

Podemos usar el componente <Form> que ya incluye el fetcher.

const Subscription = ({ asset, actionId, }: { actionId?: string; asset: Asset; }) => { const fetcher = useFetcher(); const handleSubmit = (e: FormEvent<HTMLFormElement>) => { e.preventDefault(); const formData = new FormData(e.currentTarget); fetcher.submit(formData, { method: "post", action: "/api/v1/user", }); }; return ( <fetcher.Form onSubmit={handleSubmit} className="hidden md:block"> // ...

Aquí en esta llamada a .submit es donde pasamos el formData o directamente el objeto que lleva nuestro formulario, aunque, sacarlo del form directamente me parece muy cómodo, por eso lo hago así. Sigamos entonces con:

¿Cómo mostramos la carga?

Gracias a que hemos empleado el componente <fetcher.Form>, podemos observar sus estados gracias a un prop llamado state.

const isLoading = fetcher.state !== "idle"; // "idle" | "loading" | "submitting"

Gracias a ese estado que transiciona entre "idle" | "loading" | "submitting", podemos dar feedback al usuario de que se está comunicando con el servidor.

<button disabled={isLoading} // ...

Tu puedes colocar un componente spinner de la mejor manera, por simplicidad, con este prop es suficiente para el ejemplo. Terminemos con la última pregunta.

¿Cómo recibimos la respuesta?

Pasa algo importante una vez que el fetcher alcanza el endpoint o el action path, pasa que los loaders de la ruta en la que nos encontramos son llamados nuevamente. Con esto se mantienen los datos frescos una vez que se ha mutado la base de datos.

Esto pasa automáticamente, pero, el servidor también podría colocar algunos datos dentro del fetcher para que el cliente los pueda usar. Por ejemplo: una leyenda pidiendo revisar el correo.

export const action = async ({ request }: Route.ActionArgs) => { const formData = await request.formData(); const intent = formData.get("intent"); // ... Aquí chambeas la data return { success: true } }

Esta es una ruta de recursos, es decir, una ruta que solo exporta la función action y nos funciona como una Rest API. Observa que devolvemos un objeto con la llave success a true.

Esto lo puede recibir el componente en el cliente.

if (fetcher.data?.success) { return ( <section className="flex flex-col justify-center items-center text-xl h-[100px] pt-12"> <h2>¡Gracias por suscribirte!</h2> <br /> <p>Ahora revisa tu correo. 📬</p> <EmojiConfetti /> </section> ); }

Renderizamos el mensaje de agradecimiento una vez que el fetcher ha vuelto de llevar los datos de suscripción, todo en el mismo componente en diferentes momentos, todo un full stack component ¿no crees?

Y ya, eso es todo, un intro bien simple al fetcher y a los componentes full stack con React Router.

Abrazo. Bliss.

Enlaces relacionados

RR7 Docs

meta cover

Intro a la programación funcional

Checa este otro Post

meta cover

Introducción a Vite

Checa este otro Post

¡Nuevo curso!

Animaciones web con React + Motion 🧙🏻