Skip to main content

Stores Registry

Manage reactive state using nanostores.

Overview

Stores provide reactive state management with automatic subscriptions. Uizy integrates nanostores for lightweight, framework-agnostic state.

// Register stores
uizy.start({
stores: {
user: {
name: uizy.store.atom("Guest"),
isLoggedIn: uizy.store.atom(false),
},
settings: {
theme: uizy.store.atom("light"),
language: uizy.store.atom("en"),
},
},
});

// Read values
uizy.$("user.name"); // "Guest"
uizy.$("settings.theme"); // "light"

// Update values
uizy.$("user.name", { raw: true }).set("John");
uizy.$("settings.theme", { raw: true }).set("dark");

Store Types

Uizy exposes nanostores via uizy.store:

Atom

Simple value store:

const count = uizy.store.atom(0);

count.get(); // 0
count.set(5); // Sets to 5
count.set((n) => n + 1); // Increment

Map

Object store with key-level updates:

const user = uizy.store.map({
name: "John",
email: "john@example.com",
});

user.get(); // { name: "John", email: "john@example.com" }
user.setKey("name", "Jane");

Computed

Derived values:

const firstName = uizy.store.atom("John");
const lastName = uizy.store.atom("Doe");

const fullName = uizy.store.computed(
[firstName, lastName],
(first, last) => `${first} ${last}`,
);

fullName.get(); // "John Doe"

Registration

Via start()

uizy.start({
stores: {
app: {
isLoading: uizy.store.atom(false),
error: uizy.store.atom(null),
},
user: {
profile: uizy.store.map({
id: null,
name: "",
email: "",
}),
preferences: uizy.store.map({
theme: "system",
notifications: true,
}),
},
},
});

Via uizy.state()

uizy.state({
cart: {
items: uizy.store.atom([]),
total: uizy.store.atom(0),
},
});

Accessing Stores

Get Value

uizy.$("user.name"); // Returns current value
uizy.$("settings.theme"); // "light"

Get Raw Store

Access the store object to call .set(), .subscribe(), etc:

const nameStore = uizy.$("user.name", { raw: true });

nameStore.set("Jane");
nameStore.subscribe((value) => console.log("Name changed:", value));

Silent Mode

uizy.$("nonexistent.path", { silent: true }); // undefined

Shorthand Methods

Uizy provides shorthand methods for common store operations:

uizy.$set(path, value)

Set a store value directly:

// Instead of:
uizy.$("user.name", { raw: true }).set("Jane");

// Use:
uizy.$set("user.name", "Jane");

uizy.$key(path, key, value)

Update a single key in a map store:

// Instead of:
uizy.$("user.profile", { raw: true }).setKey("age", 30);

// Use:
uizy.$key("user.profile", "age", 30);

uizy.$sub(path, callback)

Subscribe to store changes (immediate + updates). Callback is called immediately with current value, then on every change:

// Subscribe to changes
const unsubscribe = uizy.$sub("user.name", (value) => {
console.log("Name:", value); // Called immediately, then on changes
});

// Later: stop listening
unsubscribe();

uizy.$on(path, callback)

Listen to store changes (updates only). Callback is only called when value changes, not immediately:

// Listen for changes only
const unlisten = uizy.$on("user.name", (value) => {
console.log("Name changed to:", value); // NOT called immediately
});

// Later: stop listening
unlisten();

uizy.$computed(aliases, fn)

Create a computed store from multiple registered stores using aliases:

// Register stores first
uizy.state({
project: { name: uizy.store.atom("My App") },
date: { now: uizy.store.atom("2024-01-15") },
});

// Create a computed store with aliases
const $summary = uizy.$computed(
{
myAlias: "project.name",
today: "date.now",
},
({ myAlias, today }) => `${myAlias} live on ${today}`,
);

$summary.get(); // "My App live on 2024-01-15"

// Computed store updates automatically when dependencies change
uizy.$set("project.name", "New App");
$summary.get(); // "New App live on 2024-01-15"

API Reference

uizy.$(path, options?)

Get store value or raw store.

ParameterTypeDescription
pathstringDot-notation path
options.rawbooleanReturn raw store instead of value
options.silentbooleanReturn undefined if not found

uizy.$set(path, value)

Set a store value directly.

ParameterTypeDescription
pathstringDot-notation path to store
valueanyValue to set

uizy.$key(path, key, value)

Update a single key in a map store.

ParameterTypeDescription
pathstringDot-notation path to map store
keystringKey to update
valueanyValue to set

uizy.$sub(path, callback)

Subscribe to store changes. Callback is called immediately with current value, then on every change. Returns unsubscribe function.

ParameterTypeDescription
pathstringDot-notation path to store
callback(value) => voidCalled with value
Returns() => voidUnsubscribe function

uizy.$on(path, callback)

Listen to store changes. Callback is only called when value changes (not immediately). Returns unlisten function.

ParameterTypeDescription
pathstringDot-notation path to store
callback(value) => voidCalled on changes
Returns() => voidUnlisten function

uizy.$computed(aliases, fn)

Create a computed store from multiple stores using aliases. Returns a nanostore computed store.

ParameterTypeDescription
aliasesobjectObject mapping alias names to store paths
fn(values) => TFunction receiving aliased values, returns computed value
ReturnsReadableAtom<T>A computed store

uizy.state(items)

Register stores. Merges with existing stores.

uizy.store

Access to nanostores utilities:

PropertyDescription
uizy.store.atom(value)Create an atom store
uizy.store.map(object)Create a map store
uizy.store.computed(deps, fn)Create a computed store
uizy.store.deepMap(object)Create a deep map store

Store Methods

Each store has these methods:

MethodDescription
.get()Get current value
.set(value)Set value
.subscribe(fn)Subscribe to changes (immediate + updates)
.listen(fn)Listen to changes (updates only)

Map stores also have:

MethodDescription
.setKey(key, value)Update single key

Examples

User Authentication

uizy.start({
stores: {
auth: {
user: uizy.store.atom(null),
token: uizy.store.atom(null),
isAuthenticated: uizy.store.atom(false),
},
},
actions: {
auth: {
login: async (credentials) => {
const { user, token } = await api.login(credentials);

uizy.$set("auth.user", user);
uizy.$set("auth.token", token);
uizy.$set("auth.isAuthenticated", true);
},
logout: () => {
uizy.$set("auth.user", null);
uizy.$set("auth.token", null);
uizy.$set("auth.isAuthenticated", false);
},
},
},
});

// Check auth status
if (uizy.$("auth.isAuthenticated")) {
console.log("Logged in as:", uizy.$("auth.user").name);
}

Shopping Cart

uizy.state({
cart: {
items: uizy.store.atom([]),
count: uizy.store.atom(0),
total: uizy.store.atom(0),
},
});

uizy.on({
cart: {
add: (product) => {
const current = uizy.$("cart.items");
const updated = [...current, product];

uizy.$set("cart.items", updated);
uizy.$set("cart.count", updated.length);
uizy.$set(
"cart.total",
updated.reduce((sum, p) => sum + p.price, 0),
);
},
remove: (productId) => {
const updated = uizy.$("cart.items").filter((p) => p.id !== productId);

uizy.$set("cart.items", updated);
uizy.$set("cart.count", updated.length);
uizy.$set(
"cart.total",
updated.reduce((sum, p) => sum + p.price, 0),
);
},
clear: () => {
uizy.$set("cart.items", []);
uizy.$set("cart.count", 0);
uizy.$set("cart.total", 0);
},
},
});

Theme Management

uizy.state({
ui: {
theme: uizy.store.atom(localStorage.getItem("theme") || "system"),
sidebarOpen: uizy.store.atom(true),
},
});

// Subscribe to theme changes
uizy.$sub("ui.theme", (theme) => {
document.documentElement.dataset.theme = theme;
localStorage.setItem("theme", theme);
});

// Toggle theme
uizy.on({
ui: {
toggleTheme: () => {
const current = uizy.$("ui.theme");
const next = current === "light" ? "dark" : "light";
uizy.$set("ui.theme", next);
},
},
});

Form State

uizy.state({
form: {
data: uizy.store.map({
name: "",
email: "",
message: "",
}),
errors: uizy.store.map({}),
isSubmitting: uizy.store.atom(false),
},
});

uizy.on({
form: {
setField: ({ field, value }) => {
uizy.$key("form.data", field, value);
},
submit: async () => {
const data = uizy.$("form.data");
uizy.$set("form.isSubmitting", true);

try {
await api.submit(data);
uizy.emit("notification.success", "Form submitted!");
} catch (error) {
uizy.$set("form.errors", error.errors);
} finally {
uizy.$set("form.isSubmitting", false);
}
},
},
});