Saltar al contenido

Compartir estado entre islands

Al construir un sitio web con Astro usando arquitectura de islands / hidratación parcial, puede que te hayas encontrado con este problema: Quiero compartir estado entre mis componentes.

UI frameworks como React o Vue pueden fomentar providers de “context” para que otros componentes los consuman. Pero al hidratar parcialmente componentes dentro de Astro o Markdown, no puedes usar estos wrappers de context.

Astro recomienda una solución diferente para almacenamiento compartido del lado del cliente: Nano Stores.

La librería Nano Stores te permite crear stores con los que cualquier componente puede interactuar. Recomendamos Nano Stores porque:

  • Son ligeros. Nano Stores envían el mínimo JS que necesitarás (menos de 1 KB) con cero dependencias.
  • Son agnósticos al framework. ¡Esto significa que compartir estado entre frameworks será fluido! Astro está construido sobre la flexibilidad, así que nos encantan las soluciones que ofrecen una experiencia de desarrollador similar sin importar tu preferencia.

Aún así, hay una serie de alternativas que puedes explorar. Estas incluyen:

Para empezar, instala Nano Stores junto con su paquete helper para tu UI framework favorito:

Ventana de la terminal
npm install nanostores @nanostores/preact

Puedes saltar a la guía de uso de Nano Stores desde aquí, o seguir nuestro ejemplo a continuación.

Digamos que estamos construyendo una interfaz de ecommerce simple con tres elementos interactivos:

  • Un formulario de envío de “añadir al carrito”
  • Un flyout de carrito para mostrar los items añadidos
  • Un toggle para el flyout del carrito

Prueba el ejemplo completo on your machine or online via StackBlitz.

Tu archivo base de Astro podría verse así:

src/pages/index.astro
---
import CartFlyoutToggle from '../components/CartFlyoutToggle';
import CartFlyout from '../components/CartFlyout';
import AddToCartForm from '../components/AddToCartForm';
---
<!DOCTYPE html>
<html lang="en">
<head>...</head>
<body>
<header>
<nav>
<a href="/">Astro storefront</a>
<CartFlyoutToggle client:load />
</nav>
</header>
<main>
<AddToCartForm client:load>
<!-- ... -->
</AddToCartForm>
</main>
<CartFlyout client:load />
</body>
</html>

Empecemos abriendo nuestro CartFlyout cada vez que se haga clic en CartFlyoutToggle.

Primero, crea un nuevo archivo JS o TS para contener nuestro store. Usaremos un “atom” para esto:

src/cartStore.js
import { atom } from 'nanostores';
export const isCartOpen = atom(false);

Ahora, podemos importar este store en cualquier archivo que necesite leer o escribir. Empezaremos conectando nuestro CartFlyoutToggle:

src/components/CartFlyoutToggle.jsx
import { useStore } from '@nanostores/preact';
import { isCartOpen } from '../cartStore';
export default function CartButton() {
// read the store value with the `useStore` hook
const $isCartOpen = useStore(isCartOpen);
// write to the imported store using `.set`
return (
<button onClick={() => isCartOpen.set(!$isCartOpen)}>Cart</button>
)
}

Luego, podemos leer isCartOpen desde nuestro componente CartFlyout:

src/components/CartFlyout.jsx
import { useStore } from '@nanostores/preact';
import { isCartOpen } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
return $isCartOpen ? <aside>...</aside> : null;
}

Ahora, vamos a rastrear los items dentro de tu carrito. Para evitar duplicados y rastrear la “cantidad,” podemos almacenar tu carrito como un objeto con el ID del item como clave. Usaremos un Map para esto.

Añadamos un store cartItem a nuestro cartStore.js anterior. También puedes cambiar a un archivo TypeScript para definir la estructura si lo deseas.

src/cartStore.js
import { atom, map } from 'nanostores';
export const isCartOpen = atom(false);
/**
* @typedef {Object} CartItem
* @property {string} id
* @property {string} name
* @property {string} imageSrc
* @property {number} quantity
*/
/** @type {import('nanostores').MapStore<Record<string, CartItem>>} */
export const cartItems = map({});

Ahora, exportemos un helper addCartItem para que lo usen nuestros componentes.

  • Si ese item no existe en tu carrito, añade el item con una cantidad inicial de 1.
  • Si ese item existe ya, incrementa la cantidad en 1.
src/cartStore.js
...
export function addCartItem({ id, name, imageSrc }) {
const existingEntry = cartItems.get()[id];
if (existingEntry) {
cartItems.setKey(id, {
...existingEntry,
quantity: existingEntry.quantity + 1,
})
} else {
cartItems.setKey(
id,
{ id, name, imageSrc, quantity: 1 }
);
}
}

Con nuestro store en su lugar, podemos llamar esta función dentro de nuestro AddToCartForm cada vez que ese formulario se envíe. También abriremos el flyout del carrito para que puedas ver un resumen completo del carrito.

src/components/AddToCartForm.jsx
import { addCartItem, isCartOpen } from '../cartStore';
export default function AddToCartForm({ children }) {
// we'll hardcode the item info for simplicity!
const hardcodedItemInfo = {
id: 'astronaut-figurine',
name: 'Astronaut Figurine',
imageSrc: '/images/astronaut-figurine.png',
}
function addToCart(e) {
e.preventDefault();
isCartOpen.set(true);
addCartItem(hardcodedItemInfo);
}
return (
<form onSubmit={addToCart}>
{children}
</form>
)
}

Finalmente, renderizaremos esos items del carrito dentro de nuestro CartFlyout:

src/components/CartFlyout.jsx
import { useStore } from '@nanostores/preact';
import { isCartOpen, cartItems } from '../cartStore';
export default function CartFlyout() {
const $isCartOpen = useStore(isCartOpen);
const $cartItems = useStore(cartItems);
return $isCartOpen ? (
<aside>
{Object.values($cartItems).length ? (
<ul>
{Object.values($cartItems).map(cartItem => (
<li>
<img src={cartItem.imageSrc} alt={cartItem.name} />
<h3>{cartItem.name}</h3>
<p>Quantity: {cartItem.quantity}</p>
</li>
))}
</ul>
) : <p>Your cart is empty!</p>}
</aside>
) : null;
}

Ahora, deberías tener un ejemplo de ecommerce totalmente interactivo con el bundle de JS más pequeño de la galaxia 🚀

Prueba el ejemplo completo en tu máquina o en línea vía StackBlitz!

Contribuir Comunidad Patrocinar