GlyphUI’s createStore provides a lightweight global state management solution inspired by Zustand. It’s perfect for sharing state across multiple components without prop drilling.
Creating a Store
Create a store with initial state and actions:
import { createStore } from 'glyphui';
const counterStore = createStore((set) => ({
// Initial state
count: 0,
// Actions
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
incrementBy: (amount) => set((state) => ({ count: state.count + amount }))
}));
Store Creation Function
The function you pass to createStore receives two arguments:
set - Function to update the store state
get - Function to read the current state (optional)
const store = createStore((set, get) => ({
count: 0,
increment: () => {
const currentCount = get().count;
set({ count: currentCount + 1 });
}
}));
Using setState
The set function accepts either an object or a function:
Object Updates
Partial state updates are merged with existing state:
const todoStore = createStore((set) => ({
todos: [],
filter: 'all',
// Merge update - only changes 'filter'
setFilter: (filter) => set({ filter })
}));
Function Updates
Use a function when you need access to the current state:
const todoStore = createStore((set) => ({
todos: [],
addTodo: (text) => set((state) => ({
todos: [
...state.todos,
{ id: Date.now(), text, completed: false }
]
}))
}));
Replace Mode
Pass true as the second argument to replace the entire state:
const store = createStore((set) => ({
count: 0,
name: 'Counter',
// Replace the entire state
reset: () => set({ count: 0, name: 'Counter' }, true)
}));
By default, set merges updates with existing state. Use replace mode only when you need to completely replace the state object.
Store API
A store instance provides these methods:
getState()
Returns the current state:
const state = counterStore.getState();
console.log(state.count); // Current count value
setState(partial, replace)
Updates the store state (same as the set function):
// Update from outside the store
counterStore.setState({ count: 10 });
// Function update
counterStore.setState((state) => ({ count: state.count + 1 }));
subscribe(listener)
Subscribes to state changes:
const unsubscribe = counterStore.subscribe((state) => {
console.log('State changed:', state);
});
// Later, unsubscribe
unsubscribe();
destroy()
Cleans up the store and removes all listeners:
Complete Todo Store Example
import { createStore } from 'glyphui';
const todoStore = createStore((set) => ({
// State
todos: [],
newTodoText: '',
filter: 'all',
nextId: 1,
// Input actions
setNewTodoText: (text) => set({ newTodoText: text }),
// Todo manipulation actions
addTodo: () => set((state) => {
if (!state.newTodoText.trim()) return state;
return {
todos: [
...state.todos,
{
id: state.nextId,
text: state.newTodoText,
completed: false,
createdAt: Date.now()
}
],
newTodoText: '',
nextId: state.nextId + 1
};
}),
toggleTodo: (id) => set((state) => ({
todos: state.todos.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
})),
removeTodo: (id) => set((state) => ({
todos: state.todos.filter(todo => todo.id !== id)
})),
// Batch actions
clearCompleted: () => set((state) => ({
todos: state.todos.filter(todo => !todo.completed)
})),
// Filter actions
setFilter: (filter) => set({ filter }),
// Computed properties
getFilteredTodos: (state) => {
switch (state.filter) {
case 'active':
return state.todos.filter(todo => !todo.completed);
case 'completed':
return state.todos.filter(todo => todo.completed);
default:
return state.todos;
}
},
getStats: (state) => {
const total = state.todos.length;
const completed = state.todos.filter(todo => todo.completed).length;
const active = total - completed;
return { total, completed, active };
}
}));
Multiple Stores
Create separate stores for different concerns:
// Counter store
const counterStore = createStore((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 }))
}));
// Theme store
const themeStore = createStore((set) => ({
theme: {
color: '#4CAF50',
name: 'green'
},
setTheme: (theme) => set({ theme })
}));
// User store
const userStore = createStore((set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({
user,
isAuthenticated: true
}),
logout: () => set({
user: null,
isAuthenticated: false
})
}));
Separate stores help organize your application by domain. Components can subscribe to only the stores they need.
Accessing Store Actions
Store actions are regular functions you can call directly:
// Call actions from anywhere
counterStore.getState().increment();
counterStore.getState().incrementBy(5);
todoStore.getState().addTodo();
todoStore.getState().toggleTodo(42);
Or destructure for cleaner code:
const { increment, decrement, reset } = counterStore.getState();
increment();
increment();
reset();
Using Stores in Components
To use a store in components, you need to connect them. See the connect documentation for details:
import { Component, connect } from 'glyphui';
class CounterDisplay extends Component {
render(props) {
const { store } = props;
return h('div', {}, [
h('p', {}, [`Count: ${store.count}`]),
h('button', { on: { click: () => store.increment() } }, ['Increment'])
]);
}
}
// Connect component to store
const ConnectedCounterDisplay = connect(counterStore)(CounterDisplay);
Middleware and Persistence
You can add middleware by subscribing to state changes:
// Log all state changes
todoStore.subscribe((state) => {
console.log('State updated:', state);
});
// Persist to localStorage
todoStore.subscribe((state) => {
localStorage.setItem('todos', JSON.stringify(state.todos));
});
// Load from localStorage on init
const savedTodos = localStorage.getItem('todos');
if (savedTodos) {
todoStore.setState({
todos: JSON.parse(savedTodos)
});
}
When to Use Global Stores
Global stores are ideal for:
- Shared state - Data needed by multiple components
- Application settings - Theme, locale, user preferences
- User authentication - Login state, user profile
- Complex data - Lists, collections, normalized data
- Remote data - API responses, cached data
Use local state for component-specific data that doesn’t need to be shared.
Next Steps