store contract directly (name stores without the ` nanostores · Skills-SI Market prefix there), or `useStore` from `@nanostores/svelte-runes` with `.current`.\n\n### Remote data with @nanostores/query\n\n```typescript\n// stores/api.ts\nimport { atom } from \"nanostores\";\nimport { nanoquery } from \"@nanostores/query\";\n\nexport const [createFetcherStore, createMutatorStore] = nanoquery({\n fetcher: (...keys: (string | number)[]) => fetch(keys.join(\"\")).then((r) => r.json()),\n});\n\nexport const $projectId = atom\u003cstring | null>(null);\n\nexport const $projects = createFetcherStore\u003cProject[]>([\"/api/projects\"]);\n// A null key part means \"do not fetch yet\"; the store refetches when $projectId changes\nexport const $project = createFetcherStore\u003cProject>([\"/api/projects/\", $projectId]);\n\nexport const $createProject = createMutatorStore\u003c{ name: string }>(\n async ({ data, invalidate }) => {\n const res = await fetch(\"/api/projects\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(data),\n });\n invalidate(\"/api/projects\"); // dropped from cache after the request resolves\n return res.json();\n },\n);\n```\n\nFetcher state is `{ data, error, loading }`; before the first subscriber it is `{ loading: false }`. Mutators expose `mutate`. Options such as `dedupeTime` (4 s default), `cacheLifetime`, `revalidateOnFocus`, `revalidateInterval` go on `nanoquery()` or on each fetcher store.\n\n### Server-side rendering and tests\n\nSet initial values on the server (`$settings.set(initial)`), start loading with an empty listener, then `await allTasks()` before rendering. In tests call `keepMount(store)` to run `onMount`, and `cleanStores($user)` in `afterEach`.\n\n## Examples\n\n### Example 1: Shared cart between React and Vue islands\n\n**Request:** \"Share a shopping cart between my Astro React header and a Vue checkout island.\"\n\n```typescript\n// src/stores/cart.ts\nimport { computed } from \"nanostores\";\nimport { persistentMap } from \"@nanostores/persistent\";\n\nexport const $cart = persistentMap\u003cRecord\u003cstring, string>>(\"cart:\", {}); // values are strings\nexport const $cartCount = computed($cart, (cart) =>\n Object.values(cart).reduce((sum, qty) => sum + Number(qty), 0),\n);\n\nexport function addToCart(sku: string) {\n $cart.setKey(sku, String(Number($cart.get()[sku] ?? 0) + 1));\n}\n```\n\nReact header: `const count = useStore($cartCount);` Vue checkout: `const count = useStore($cartCount);` Clicking \"Add\" in either island updates both, and other browser tabs, because persistent stores sync through `localStorage`.\n\n### Example 2: Search box that fetches only for real input\n\n**Request:** \"Query the API when the search text changes, but not for empty input.\"\n\n```typescript\nimport { atom, computed } from \"nanostores\";\nimport { createFetcherStore } from \"./api\";\n\nexport const $query = atom(\"\");\nconst $searchKey = computed($query, (q) => (q.length >= 2 ? encodeURIComponent(q) : null));\n\nexport const $results = createFetcherStore\u003cProduct[]>([\"/api/products?q=\", $searchKey]);\n```\n\n`$results` stays idle (`loading: false`, no data) until the text has two characters; then `$query.set(\"oat milk\")` fetches `/api/products?q=oat%20milk` and caches it by key.\n\n## Guidelines\n\n- Name stores with a ` nanostores · Skills-SI Market prefix (except in Svelte) and keep actions as plain exported functions next to the store; move logic out of components.\n- Prefer `useStore($computed)` over selecting inside the component so only the derived value triggers renders.\n- Do not call `get()` in components or during render; it does not subscribe. Use it in actions and tests.\n- Persistent store values are strings (or JSON with the `encode`/`decode` option); do not store secrets in `localStorage`.\n- A fetcher key with a `null`, `undefined` or `false` part is skipped; use that for conditional fetching.\n- Stores are module singletons: on the server, reset them per request or avoid per-user data in global stores.\n- For heavy server-state needs (infinite queries, devtools, complex mutations) TanStack Query may fit better; nanostores shines for small, cross-framework state.\n","licenseDocuments":[]}}
L’interface est localisée ; les présentations et documents des Skills sont traduits automatiquement par IA dans votre langue. Le code, les commandes, chemins et termes techniques restent en version originale, et le document de l’auteur fait foi.
Explorer les Skills
Données et recherche · SKILL.md

nanostores

nanostores

Nano Stores is a tiny, framework-agnostic state manager built from many small atomic stores (atoms, maps, computed stores) with lazy subscriptions. Use when the user wants shared state in React, Preact, Vue, Svelte, Solid, Lit, Angular or vanilla JS, needs derived or async data stores, wants to share state between islands in Astro, or asks how to migrate fr…

Independent GitHub snapshot · Automated classification · Original documentation governs

Tterminalskills / skillsApache-2.0

Documentation de la Skill

GitHub

name: nanostores description: >- Nano Stores is a tiny, framework-agnostic state manager built from many small atomic stores (atoms, maps, computed stores) with lazy subscriptions. Use when the user wants shared state in React, Preact, Vue, Svelte, Solid, Lit, Angular or vanilla JS, needs derived or async data stores, wants to share state between islands in Astro, or asks how to migrate from Redux, Zustand or Context to nanostores. license: Apache-2.0 compatibility: "Node.js 20+ and npm; ESM package. @nanostores/react 2.x needs React 18+ and nanostores 1.2+." metadata: author: terminal-skills version: "1.1.0" category: development repository: https://github.com/nanostores/nanostores tags: - state-management - framework-agnostic - react - vue - svelte

Nanostores — Tiny State Manager

Overview

Nano Stores (package nanostores, currently 1.x) keeps state in many small stores instead of one big tree. The core is 351 to 844 bytes minified and brotlied depending on what you import, has no dependencies, and tree-shakes. Stores are lazy: a store has a "mount" mode while it has listeners and a "disabled" mode otherwise (it switches off one second after the last listener leaves), so network connections and timers run only while the UI needs them. Bindings exist for React, Preact, Vue, Svelte, Solid, Lit, Angular, and Alpine.js.

Instructions

Install

npm install nanostores
npm install @nanostores/react     # or @nanostores/preact | vue | solid | lit | angular | svelte-runes
npm install @nanostores/query     # optional: cached remote data fetching
npm install @nanostores/persistent  # optional: localStorage sync across tabs

Atoms, maps, computed

// stores/session.ts
import { atom, map, computed, onMount, batch } from "nanostores";

export const $theme = atom<"light" | "dark">("light");

export const $user = map<{ name: string; email: string; plan: "free" | "pro" }>({
  name: "",
  email: "",
  plan: "free",
});

export const $isPro = computed($user, (user) => user.plan === "pro");
export const $greeting = computed(
  [$user, $theme],
  (user, theme) => `${user.name || "Guest"} (${theme} mode)`,
);

// Lazy loading: runs when the first listener appears, cleanup when the last leaves
onMount($user, () => {
  fetch("/api/me").then((r) => r.json()).then((me) => $user.set(me));
  return () => { /* cleanup */ };
});

$user.setKey("plan", "pro");   // change one key; listenKeys() subscribers of other keys stay quiet
$theme.set("dark");
batch(() => {                  // several writes, one notification round
  $user.setKey("name", "Mara Lindqvist");
  $theme.set("light");
});

Useful API: store.get(), store.set(), store.subscribe(cb) (calls immediately), store.listen(cb) (only on change), listenKeys(map, ["name"], cb), effect([$a, $b], cb) for side effects, batched() for computed values that update once per tick, onSet(store, ({ newValue, abort }) => ...) for validation. Values are compared with Object.is; set store.eq to a deep-equal function for objects.

React

import { useStore } from "@nanostores/react";
import { $user, $isPro } from "../stores/session";

export function UserProfile() {
  const user = useStore($user);
  const isPro = useStore($isPro);
  return (
    <div>
      <p>{user.email}</p>
      {isPro && <span className="badge">PRO</span>}
      <button onClick={() => $user.setKey("plan", "pro")}>Upgrade</button>
    </div>
  );
}

Other frameworks: Vue useStore() from @nanostores/vue; Solid useStore() returns an accessor (profile().name); Svelte components can use the $ store contract directly (name stores without the $ prefix there), or useStore from @nanostores/svelte-runes with .current.

Remote data with @nanostores/query

// stores/api.ts
import { atom } from "nanostores";
import { nanoquery } from "@nanostores/query";

export const [createFetcherStore, createMutatorStore] = nanoquery({
  fetcher: (...keys: (string | number)[]) => fetch(keys.join("")).then((r) => r.json()),
});

export const $projectId = atom<string | null>(null);

export const $projects = createFetcherStore<Project[]>(["/api/projects"]);
// A null key part means "do not fetch yet"; the store refetches when $projectId changes
export const $project = createFetcherStore<Project>(["/api/projects/", $projectId]);

export const $createProject = createMutatorStore<{ name: string }>(
  async ({ data, invalidate }) => {
    const res = await fetch("/api/projects", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(data),
    });
    invalidate("/api/projects");   // dropped from cache after the request resolves
    return res.json();
  },
);

Fetcher state is { data, error, loading }; before the first subscriber it is { loading: false }. Mutators expose mutate. Options such as dedupeTime (4 s default), cacheLifetime, revalidateOnFocus, revalidateInterval go on nanoquery() or on each fetcher store.

Server-side rendering and tests

Set initial values on the server ($settings.set(initial)), start loading with an empty listener, then await allTasks() before rendering. In tests call keepMount(store) to run onMount, and cleanStores($user) in afterEach.

Examples

Example 1: Shared cart between React and Vue islands

Request: "Share a shopping cart between my Astro React header and a Vue checkout island."

// src/stores/cart.ts
import { computed } from "nanostores";
import { persistentMap } from "@nanostores/persistent";

export const $cart = persistentMap<Record<string, string>>("cart:", {});   // values are strings
export const $cartCount = computed($cart, (cart) =>
  Object.values(cart).reduce((sum, qty) => sum + Number(qty), 0),
);

export function addToCart(sku: string) {
  $cart.setKey(sku, String(Number($cart.get()[sku] ?? 0) + 1));
}

React header: const count = useStore($cartCount); Vue checkout: const count = useStore($cartCount); Clicking "Add" in either island updates both, and other browser tabs, because persistent stores sync through localStorage.

Example 2: Search box that fetches only for real input

Request: "Query the API when the search text changes, but not for empty input."

import { atom, computed } from "nanostores";
import { createFetcherStore } from "./api";

export const $query = atom("");
const $searchKey = computed($query, (q) => (q.length >= 2 ? encodeURIComponent(q) : null));

export const $results = createFetcherStore<Product[]>(["/api/products?q=", $searchKey]);

$results stays idle (loading: false, no data) until the text has two characters; then $query.set("oat milk") fetches /api/products?q=oat%20milk and caches it by key.

Guidelines

  • Name stores with a $ prefix (except in Svelte) and keep actions as plain exported functions next to the store; move logic out of components.
  • Prefer useStore($computed) over selecting inside the component so only the derived value triggers renders.
  • Do not call get() in components or during render; it does not subscribe. Use it in actions and tests.
  • Persistent store values are strings (or JSON with the encode/decode option); do not store secrets in localStorage.
  • A fetcher key with a null, undefined or false part is skipped; use that for conditional fetching.
  • Stores are module singletons: on the server, reset them per request or avoid per-user data in global stores.
  • For heavy server-state needs (infinite queries, devtools, complex mutations) TanStack Query may fit better; nanostores shines for small, cross-framework state.
Skills enregistrées