Complete reference for the Eleva Store plugin.
| Property | Type | Description |
|---|---|---|
store.state |
Object |
Reactive state object with Signal properties |
Executes an action to mutate state.
// Signature
store.dispatch(actionName: string, payload?: any): Promise<any>
// Examples
await store.dispatch("increment");
await store.dispatch("setUser", { name: "John", email: "john@example.com" });
await store.dispatch("auth.login", { username: "john", password: "secret" });
const user = await store.dispatch("fetchUser", 123);
Subscribes to all state mutations. Returns an unsubscribe function.
// Signature
store.subscribe(callback: (mutation, state) => void): () => void
// Example
const unsubscribe = store.subscribe((mutation, state) => {
console.log("Action:", mutation.type);
console.log("Payload:", mutation.payload);
console.log("Timestamp:", mutation.timestamp);
console.log("New state:", state);
});
// Later: stop listening
unsubscribe();
Returns a deep copy of current state values (non-reactive snapshot).
// Signature
store.getState(): Object
// Example
const snapshot = store.getState();
console.log(snapshot);
// { count: 5, user: { name: "John" }, auth: { token: "abc" } }
// Useful for debugging, logging, or SSR hydration
localStorage.setItem("debug-state", JSON.stringify(store.getState()));
Replaces the entire state. Useful for hydration or time-travel debugging.
// Signature
store.replaceState(newState: Object): void
// Example
store.replaceState({
count: 10,
user: { name: "Jane", email: "jane@example.com" },
theme: "dark"
});
Dynamically registers a new namespaced module.
// Signature
store.registerModule(namespace: string, module: { state: Object, actions: Object }): void
// Example
store.registerModule("wishlist", {
state: {
items: []
},
actions: {
addItem: (state, item) => {
state.wishlist.items.value = [...state.wishlist.items.value, item];
},
removeItem: (state, itemId) => {
state.wishlist.items.value = state.wishlist.items.value.filter(i => i.id !== itemId);
}
}
});
// Now accessible
store.state.wishlist.items.value;
store.dispatch("wishlist.addItem", { id: 1, name: "Product" });
Removes a dynamically registered module.
// Signature
store.unregisterModule(namespace: string): void
// Example
store.unregisterModule("wishlist");
// store.state.wishlist is now undefined
Creates a new state property at runtime.
// Signature
store.createState(key: string, initialValue: any): Signal
// Example
const theme = store.createState("theme", "dark");
console.log(theme.value); // "dark"
console.log(store.state.theme.value); // "dark"
Creates a new action at runtime.
// Signature
store.createAction(name: string, actionFn: (state, payload?) => void): void
// Example
store.createAction("toggleTheme", (state) => {
state.theme.value = state.theme.value === "dark" ? "light" : "dark";
});
store.dispatch("toggleTheme");
Clears persisted state from storage.
// Signature
store.clearPersistedState(): void
// Example
store.clearPersistedState();
// localStorage/sessionStorage entry is removed
The plugin also adds shortcuts to the Eleva instance:
// These are equivalent:
app.store.dispatch("increment");
app.dispatch("increment");
app.store.getState();
app.getState();
app.store.subscribe(callback);
app.subscribe(callback);
app.store.createAction("foo", fn);
app.createAction("foo", fn);
The Store plugin includes TypeScript type definitions for full type safety.
import Eleva from "eleva";
import { Store } from "eleva/plugins";
// Define your state shape
interface AppState {
count: number;
user: User | null;
theme: "light" | "dark";
}
interface User {
id: number;
name: string;
email: string;
}
// Define action payloads
interface ActionPayloads {
increment: void;
setCount: number;
setUser: User | null;
setTheme: "light" | "dark";
}
const app = new Eleva("TypedApp");
app.use(Store, {
state: {
count: 0,
user: null,
theme: "light"
} as AppState,
actions: {
increment: (state) => {
state.count.value++;
},
setCount: (state, value: number) => {
state.count.value = value;
},
setUser: (state, user: User | null) => {
state.user.value = user;
},
setTheme: (state, theme: "light" | "dark") => {
state.theme.value = theme;
}
}
});
// Type-safe dispatch
app.store.dispatch("setCount", 10);
app.store.dispatch("setUser", { id: 1, name: "John", email: "john@example.com" });
interface ComponentContext {
store: {
state: {
count: Signal<number>;
user: Signal<User | null>;
};
dispatch: (action: string, payload?: unknown) => Promise<unknown>;
};
signal: <T>(value: T) => Signal<T>;
}
app.component("TypedComponent", {
setup({ store, signal }: ComponentContext) {
const localValue = signal<string>("");
const increment = () => store.dispatch("increment");
const setUser = (user: User) => store.dispatch("setUser", user);
return {
count: store.state.count,
user: store.state.user,
localValue,
increment,
setUser
};
},
template: (ctx) => `
<div>
<p>Count: ${ctx.count.value}</p>
<p>User: ${ctx.user.value?.name || "Guest"}</p>
</div>
`
});
interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
}
interface CartState {
items: CartItem[];
total: number;
}
interface CartItem {
id: number;
name: string;
price: number;
quantity: number;
}
app.use(Store, {
namespaces: {
auth: {
state: {
user: null,
token: null,
isAuthenticated: false
} as AuthState,
actions: {
login: (state, payload: { user: User; token: string }) => {
state.auth.user.value = payload.user;
state.auth.token.value = payload.token;
state.auth.isAuthenticated.value = true;
},
logout: (state) => {
state.auth.user.value = null;
state.auth.token.value = null;
state.auth.isAuthenticated.value = false;
}
}
},
cart: {
state: {
items: [],
total: 0
} as CartState,
actions: {
addItem: (state, item: CartItem) => {
state.cart.items.value = [...state.cart.items.value, item];
state.cart.total.value += item.price * item.quantity;
}
}
}
}
});
Problem: Component doesn’t re-render when state changes.
Solutions:
// 1. Make sure you're using .value
// Wrong
template: (ctx) => `<p>${ctx.count}</p>`
// Right
template: (ctx) => `<p>${ctx.count.value}</p>`
// 2. Make sure you're returning state from setup
setup({ store }) {
return {
count: store.state.count // Must return the signal
};
}
// 3. For arrays/objects, create new references
// Wrong - mutating existing array
state.todos.value.push(newTodo);
// Right - creating new array
state.todos.value = [...state.todos.value, newTodo];
Problem: Error: Action "actionName" not found
Solutions:
// 1. Check action name spelling
store.dispatch("increment"); // Must match exactly
// 2. For namespaced actions, use dot notation
store.dispatch("auth.login", payload); // Not "authLogin"
// 3. Make sure action is defined
app.use(Store, {
actions: {
increment: (state) => state.count.value++ // Must exist
}
});
Problem: State not being saved/loaded from storage.
Solutions:
// 1. Check persistence is enabled
persistence: {
enabled: true, // Must be true
key: "my-store"
}
// 2. Check include/exclude paths
persistence: {
include: ["theme"] // Only these are persisted
}
// 3. Check storage availability
if (typeof window !== "undefined" && window.localStorage) {
// Storage is available
}
// 4. Check for storage quota errors
onError: (error, context) => {
if (error.name === "QuotaExceededError") {
console.warn("Storage quota exceeded");
}
}
Problem: Module "name" already exists warning.
Solution:
// Check before registering
if (!store.state.myModule) {
store.registerModule("myModule", { /* ... */ });
}
// Or unregister first
store.unregisterModule("myModule");
store.registerModule("myModule", { /* ... */ });
Problem: Infinite loop of state updates.
Solution:
// Avoid updating state in watchers that trigger re-renders
// Wrong
store.state.count.watch(() => {
store.state.count.value++; // Infinite loop!
});
// Right - use conditions
store.state.count.watch((value) => {
if (value < 100) {
// Safe conditional update
}
});
| Vuex | Eleva Store |
|---|---|
state: { count: 0 } |
state: { count: 0 } |
mutations |
actions (combined) |
actions |
actions (async supported) |
getters |
Computed in components |
modules |
namespaces |
this.$store.state.count |
store.state.count.value |
this.$store.commit('increment') |
store.dispatch('increment') |
this.$store.dispatch('fetchData') |
store.dispatch('fetchData') |
mapState, mapGetters |
Destructure in setup() |
| Redux | Eleva Store |
|---|---|
createStore(reducer) |
app.use(Store, { state, actions }) |
| Reducers (pure functions) | actions (mutate directly) |
store.getState() |
store.getState() or store.state.*.value |
store.dispatch({ type, payload }) |
store.dispatch("type", payload) |
store.subscribe() |
store.subscribe() |
combineReducers |
namespaces |
connect() HOC |
Access in setup({ store }) |
| Middleware | subscribe() + custom logic |
| Redux Thunk | Actions support async natively |
| MobX | Eleva Store |
|---|---|
observable({ count: 0 }) |
state: { count: 0 } (auto-wrapped in Signals) |
action decorator |
Define in actions object |
computed |
Compute in components |
observer HOC |
Not needed (automatic) |
autorun |
store.subscribe() |
runInAction |
Just call dispatch() |
| Metric | Value |
|---|---|
| Bundle Size | ~6KB minified |
| API Methods | 10 (dispatch, subscribe, getState, etc.) |
| Storage Options | 2 (localStorage, sessionStorage) |
| State Access | Direct via Signals |
| Async Support | Native (actions can be async) |
| Method | Purpose |
|---|---|
store.state |
Access reactive state |
store.dispatch(action, payload) |
Execute action |
store.subscribe(callback) |
Listen to mutations |
store.getState() |
Get state snapshot |
store.replaceState(state) |
Replace all state |
store.registerModule(name, module) |
Add module |
store.unregisterModule(name) |
Remove module |
store.createState(key, value) |
Add state property |
store.createAction(name, fn) |
Add action |
store.clearPersistedState() |
Clear storage |
For questions or issues, visit the GitHub repository.
| ← Back to Advanced | Back to Store Overview | Examples → |