useState
Devuelve la señal de estado compartido de la aplicación con una clave, creada la primera vez, por petición en SSR y transferida al navegador.
Se autoimporta en las aplicaciones del framework.
import { useState } from "@meekojs/meeko/framework";Una signal creada en un componente es suya: otro componente no la ve. useState(key, factory) da una señal de la aplicación, una por clave: el primer componente que pide "cart:items" la crea con factory(), y todos los demás que piden la misma clave reciben la misma señal. Es la base de los stores de app/stores.
En SSR hay un almacén por petición, así que dos usuarios nunca comparten estado; su contenido viaja en el payload y el navegador lo restaura antes de hidratar. En el navegador hay uno por aplicación montada, que sobrevive a las navegaciones. Se autoimporta en las aplicaciones.
Uso#
// app/stores/cart.ts
import { useState } from "@meekojs/meeko/framework";
export function useCart() {
const items = useState("cart:items", (): string[] => []);
const add = (id: string): void => { items.value = [...items.value, id]; };
return { items, add };
}<!-- app/components/CartBadge.meeko -->
<script lang="ts">
const { items } = useCart();
</script>
<template><span class="badge">{items.value.length}</span></template>Cualquier componente que llame a useCart() lee y escribe la misma lista.
Tipo#
function useState<T>(key: string, factory: () => T): Signal<T>class StateStore {
constructor(initial?: Readonly<Record<string, unknown>>);
get sealed(): boolean;
seal(): void;
get<T>(key: string, factory: () => T): Signal<T>;
seed(values: Readonly<Record<string, unknown>>): void;
seedForNavigation<T>(values: Readonly<Record<string, unknown>>, commit: () => T): T;
navigationDraft(): { state: StateStore; commit<T>(work: () => T): T };
snapshot(): Record<string, unknown>;
}Parámetros#
| Parámetro | Tipo | Por defecto | Descripción |
|---|---|---|---|
key |
string |
obligatorio | Clave única en la aplicación, como "cart:items". No puede estar vacía ni ser __proto__, constructor o prototype (TypeError: Invalid state key). |
factory |
() => T |
obligatorio | Crea el valor inicial la primera vez que se pide la clave. Debe devolver algo que JSON represente fielmente. |
Valor devuelto#
La Signal<T> compartida de esa clave. Fuera de una aplicación de Meeko lanza Error: Missing Meeko context: Meeko framework state.
Ejemplos#
Un store con estado derivado#
// app/stores/session.ts
import { computed } from "@meekojs/meeko";
import { useState } from "@meekojs/meeko/framework";
export function useSessionStore() {
const user = useState<{ id: string; name: string } | null>("session:user", () => null);
const signedIn = computed(() => user.value !== null);
return { user, signedIn, signOut: () => { user.value = null; } };
}Estado sembrado en el servidor#
El servidor llena el estado durante el render; el navegador lo recibe en el payload y no repite el trabajo:
<!-- app/app.meeko -->
<script lang="ts">
const visits = useState("app:renderedAt", () => new Date().toISOString());
</script>
<template>
<div class="site-root"><MeekoOutlet /></div>
<footer>Renderizado: {visits.value}</footer>
</template>factory se ejecuta en el servidor; al hidratar, el navegador ya tiene la clave y no la vuelve a llamar, así que el texto coincide.
Compartir entre páginas#
En el navegador, el almacén vive mientras la aplicación está montada: un filtro elegido en una página sigue ahí al volver a ella.
// app/stores/filters.ts
import { useState } from "@meekojs/meeko/framework";
export const useFilters = () => useState("products:filters", () => ({ category: "todas", sort: "precio" }));Errores comunes#
Un valor que JSON no representa#
const when = useState("agenda:when", () => new Date());El estado viaja en JSON: al serializar el payload la respuesta falla con TypeError: Non-plain object at $.when. Guarda el texto y deriva el objeto:
const when = useState("agenda:when", () => new Date().toISOString());
const date = computed(() => new Date(when.value));Pasar el valor en lugar de la función#
const count = useState<number>("cart:count", 0);meeko check da TS2345: Argument of type 'number' is not assignable to parameter of type '() => number'.
const count = useState("cart:count", () => 0);Mutar el array o el objeto#
items.value.push(id);No hay error, pero la señal no avisa: ni la plantilla ni los demás componentes se enteran. Asigna un valor nuevo:
items.value = [...items.value, id];Escribir el estado cuando la página ya salió#
En el servidor el almacén se sella tras el render: el payload ya está escrito.
// En el script de un componente: una promesa suelta que escribe después del render.
const stats = useState<Stats | null>("stats", () => null);
fetchStats().then((value) => { stats.value = value; });En SSR, la escritura tardía lanza STATE_SEALED: Meeko state "stats" cannot change after the page was rendered; the browser already received it y, sin un catch, es un rechazo sin manejar. Carga el dato antes del render, en <script server>, load o con await useApi:
const stats = await useRpcQuery("stats.summary");Notas#
- Por petición. En el servidor, cada petición tiene su almacén: nunca guardes estado de la aplicación en una variable de módulo, que sí se comparte entre usuarios.
- Hidratación. El navegador restaura el estado del payload antes de hidratar, así que
factoryno se vuelve a ejecutar para las claves que el servidor ya creó. - Navegación. En una navegación del cliente, las claves nuevas que crea el servidor se añaden sin sustituir las existentes. Un loader de una ruta
render: "client"escribe en un estado provisional que se aplica al confirmar la ruta. - Persistencia. El estado vive en memoria: una recarga lo vuelve a crear. Para sobrevivir a recargas o compartir entre pestañas, usa
useStorage. - Seguridad. Todo lo que guardas viaja en el HTML de la página: no pongas ahí secretos ni datos de otros usuarios.
- Dónde llamarlo. En el script de un componente o en un composable o store que éste llama mientras se ejecuta. El compilador no lo limita al nivel superior (no da
MEEK054), pero busca el almacén en la raíz activa: en un manejador de eventos no la hay y lanzaMissing Meeko context. Pide la señal en el script y úsala en el manejador.