Overview
The createActions function creates a set of action creators that are automatically bound to a store. Actions receive the current state as their first argument and can return state updates that are automatically applied to the store.
This pattern separates action logic from the store definition, making it easier to organize complex state management logic.
Signature
function createActions ( store , actions )
The store object created with createStore.
An object where keys are action names and values are action functions. Each action function receives:
state - The current store state
...args - Any additional arguments passed when calling the action
Action Signature: ( state , ... args ) => stateUpdates | void
If the action returns an object, it is automatically passed to store.setState().
Return Value
Returns an object with bound action functions. Each function:
Gets the current state from the store
Calls the original action function with (state, ...args)
If the result is an object, calls store.setState(result)
Returns the result
Examples
Basic Action Creators
import { createStore , createActions } from '@glyphui/runtime' ;
// Create a simple store
const counterStore = createStore (() => ({
count: 0
}));
// Define actions separately
const counterActions = createActions ( counterStore , {
increment : ( state ) => ({
count: state . count + 1
}),
decrement : ( state ) => ({
count: state . count - 1
}),
incrementBy : ( state , amount ) => ({
count: state . count + amount
}),
reset : () => ({
count: 0
})
});
// Use the actions
counterActions . increment ();
console . log ( counterStore . getState (). count ); // 1
counterActions . incrementBy ( 5 );
console . log ( counterStore . getState (). count ); // 6
counterActions . reset ();
console . log ( counterStore . getState (). count ); // 0
Actions with Side Effects
Actions can perform side effects before returning state updates:
const todoStore = createStore (() => ({
todos: [],
isLoading: false ,
error: null
}));
const todoActions = createActions ( todoStore , {
// Synchronous action
addTodo : ( state , text ) => ({
todos: [ ... state . todos , { id: Date . now (), text , completed: false }]
}),
// Action with side effects
loadTodos : async ( state ) => {
// Set loading state
todoStore . setState ({ isLoading: true , error: null });
try {
const todos = await api . fetchTodos ();
// Return final state update
return { todos , isLoading: false };
} catch ( error ) {
// Handle errors
return { error: error . message , isLoading: false };
}
},
// Action that doesn't return state
logTodoCount : ( state ) => {
console . log ( 'Total todos:' , state . todos . length );
// No return value = no state update
}
});
// Use async actions
await todoActions . loadTodos ();
todoActions . addTodo ( 'Buy groceries' );
todoActions . logTodoCount (); // Logs but doesn't update state
Complex State Updates
const cartStore = createStore (() => ({
items: [],
total: 0
}));
const cartActions = createActions ( cartStore , {
addItem : ( state , item ) => {
const existingItem = state . items . find ( i => i . id === item . id );
let newItems ;
if ( existingItem ) {
// Increment quantity if item exists
newItems = state . items . map ( i =>
i . id === item . id ? { ... i , quantity: i . quantity + 1 } : i
);
} else {
// Add new item
newItems = [ ... state . items , { ... item , quantity: 1 }];
}
// Recalculate total
const total = newItems . reduce (
( sum , item ) => sum + item . price * item . quantity ,
0
);
return { items: newItems , total };
},
removeItem : ( state , itemId ) => {
const newItems = state . items . filter ( i => i . id !== itemId );
const total = newItems . reduce (
( sum , item ) => sum + item . price * item . quantity ,
0
);
return { items: newItems , total };
},
clearCart : () => ({
items: [],
total: 0
})
});
Organizing Actions by Domain
const appStore = createStore (() => ({
user: null ,
todos: [],
settings: { theme: 'light' }
}));
// Group related actions
const userActions = createActions ( appStore , {
login : ( state , credentials ) => ({
user: { id: 1 , name: credentials . username }
}),
logout : () => ({
user: null
})
});
const todoActions = createActions ( appStore , {
addTodo : ( state , text ) => ({
todos: [ ... state . todos , { id: Date . now (), text }]
}),
removeTodo : ( state , id ) => ({
todos: state . todos . filter ( t => t . id !== id )
})
});
const settingsActions = createActions ( appStore , {
setTheme : ( state , theme ) => ({
settings: { ... state . settings , theme }
})
});
// Use organized actions
userActions . login ({ username: 'alice' });
todoActions . addTodo ( 'Learn GlyphUI' );
settingsActions . setTheme ( 'dark' );
When to Use createActions
Use createActions When:
Separating Concerns You want to keep action logic separate from the store definition for better organization.
Shared Actions Multiple parts of your app need access to the same actions without importing the entire store.
Testing You want to test action logic independently from components.
Complex Logic Actions have complex logic that would clutter the store definition.
Use Store Actions (createStore) When:
Simple Stores The store is simple and keeping actions inline is clearer.
Encapsulation Actions should be tightly coupled to the store and not used elsewhere.
Comparison: createActions vs Store Actions
Store Actions (Inline)
const store = createStore (( setState , getState ) => ({
count: 0 ,
increment : () => {
const state = getState ();
setState ({ count: state . count + 1 });
}
}));
// Call directly from store state
store . getState (). increment ();
Separate Actions (createActions)
const store = createStore (() => ({
count: 0
}));
const actions = createActions ( store , {
increment : ( state ) => ({ count: state . count + 1 })
});
// Call from actions object
actions . increment ();
Both approaches are valid. Choose based on your needs:
Store actions : More concise, actions are part of state
createActions : More flexible, easier to test and organize
Action Patterns
Returning Partial Updates
Actions can return partial state updates that will be merged:
const actions = createActions ( store , {
// Only updates 'count', leaves other state unchanged
increment : ( state ) => ({ count: state . count + 1 })
});
Multiple State Updates
For multiple state updates, call setState directly:
const actions = createActions ( store , {
complexUpdate : ( state ) => {
// Intermediate update
store . setState ({ isLoading: true });
// Do work...
// Final update
return { data: newData , isLoading: false };
}
});
Conditional Updates
const actions = createActions ( store , {
toggleFeature : ( state , featureName ) => {
// Only update if feature exists
if ( ! state . features [ featureName ]) {
console . warn ( 'Feature not found' );
return ; // No state update
}
return {
features: {
... state . features ,
[featureName]: ! state . features [ featureName ]
}
};
}
});
Best Practices
Keep Actions Pure Actions should be predictable. Given the same state and arguments, they should produce the same result.
Return New Objects Always return new objects/arrays rather than mutating the state directly to ensure proper reactivity.
Handle Errors For async actions, catch errors and update the state accordingly. Consider an error field in your state.
Use Descriptive Names Action names should clearly describe what they do: addTodo, removeUser, toggleTheme.
See Also
createStore - Create a state management store
connect - Connect a store to GlyphUI components