Store overview
Shared reactive state for ilha islands — install @ilha/store, build a store, and read/write it from any island.
@ilha/store is shared reactive state for ilha apps. It sits outside any island and uses alien-signals — the same engine as island .state() — so stores and islands share one reactive graph without bridging.
Island .state() is local to one component. Use @ilha/store when state must be shared across islands or updated from non-island code.
Install
npm install @ilha/storepnpm add @ilha/storeyarn add @ilha/storebun add @ilha/storeilha is an optional peer dependency. Install both so bind:* directives and signal tracking work in templates.
Import paths
| Import path | Use it for |
|---|---|
@ilha/store |
store, store types, subscriptions, select, bind, persist, dehydrate/hydrate, shallowEqual, and effectScope. |
@ilha/store/persist |
persist, persistQuery, querySpec, codec, readQuery, and URL/localStorage persistence helpers. |
@ilha/store/query |
query, QueryCache, and defaultQueryCache — data-fetching cache for async .derived(). |
@ilha/store/form |
Form extraction, validation, and issue-to-error mapping. See Forms. |
Quick start
import { store } from "@ilha/store";
const counterStore = store({ count: 0, label: "counter" })
.derived("doubled", ({ get }) => get().count * 2)
.middleware((patch, {}, next) => {
// guard: floor count at zero
if (patch.count !== undefined && patch.count < 0) return;
next(patch);
})
.action("increment", (_, { get }) => ({
count: get().count + 1,
}))
.action("decrement", (_, { get }) => ({
count: get().count - 1,
}))
.action("setLabel", (label: string) => ({ label }))
.on("change", (state) => {
localStorage.setItem("counter", JSON.stringify(state));
})
.build();
counterStore.count(); // 0 — reactive read
counterStore.count(5); // write → goes through middleware
counterStore.doubled(); // 10 — reactive derived
counterStore.increment(); // 6
counterStore.getState(); // { count: 6, label: "counter" }
When to use
Choose @ilha/store when state needs to be:
- Shared across multiple islands — e.g. a cart, auth session, or active theme
- Updated from outside an island — e.g. from a WebSocket handler or global event bus
- Persisted or derived globally — e.g. synced to
localStoragevia.on("change", …) - Form state — pair with the Form helpers for typed validation and error mapping
Use island .state() when only one island reads and writes a piece of state.
Usage with ilha islands
State and derived accessors are signal-shaped — use them directly inside .render(), .derived(), and .effect() without a wrapper:
import { store } from "@ilha/store";
import { ilha } from "ilha";
const cartStore = store({ items: [] as string[] })
.action("add", (item: string, { get }) => ({
items: [...get().items, item],
}))
.derived("count", ({ get }) => get().items.length)
.build();
export const CartBadge = ilha(() => (
<span>{cartStore.count()}</span>
));
export const CartList = ilha(() => (
<ul>
{cartStore.items().map((item) => (
<li key={item}>{item}</li>
))}
</ul>
));
Both islands stay in sync. CartBadge re-renders only when count changes; CartList only when items changes. Use .select() for ad-hoc projections and .bind() for two-way form fields — see Subscriptions and validation.
Related
| Topic | Guide |
|---|---|
| Island-local state | State |
| Derived values and actions | Derived state and actions |
| Subscriptions, select, validation | Subscriptions and validation |
| Forms | Forms |
| Persistence and query cache | Persistence and query |
| SSR transfer | SSR |