ActionStack V3 is a minimal yet powerful state management system designed for reactive applications, built on top of Streamix. It supports modular state slices, synchronous and asynchronous actions (thunks), selectors, data pipelines, and fine-grained control via middleware and execution stack.
redux-docs / observable-docs / saga-docs /
- ✅ Modular slice-based store architecture
- 🔁 Built-in support for both sync and async (thunk) actions
- 🔄 Reactive state streams via Streamix
- 🧩 Feature modules with co-located state, logic, and dependencies
- ⚙️ Middleware, locking, and concurrency strategies
- 🧠 Type-safe selectors and action creators
npm i @actioncrew/actionstack
To create a store, use the createStore function, which initializes the store with the provided main module and optional settings or enhancers.
import { createStore } from '@actioncrew/actionstack';
// Optional: Define store settings to customize behavior
const storeSettings = {
dispatchSystemActions: false,
enableGlobalReducers: false,
awaitStatePropagation: true,
exclusiveActionProcessing: false
};
// Create the store instance
const store = createStore({
reducers: [rootReducer],
}, storeSettings, applyMiddleware(logger, epics));
In V3, state changes are managed through action handlers defined directly on action creators. When creating an action with createAction, you can optionally provide a handler function that specifies how the state should update when that action is dispatched. These handlers are automatically collected and associated with their respective feature modules when you register the actions, so there is no need for a separate actionHandlers property. This approach keeps state update logic colocated with actions, making your code more modular and easier to maintain.
const increment = createAction('increment', (state: number, payload: number) => state + payload);
const counterModule = createModule({
slice: 'counter',
initialState: 0,
actions: { increment }
});
Modules can be loaded or unloaded dynamically. The loadModule and unloadModule methods manage this process, ensuring that the store’s dependencies are correctly updated.
const featureModule = createModule({
slice: 'superModule',
initialState: {},
dependencies: { heroService: new HeroService() }
});
// Load a feature module
featureModule.init(store);
// Unload a feature module (with optional state clearing)
featureModule.destroy(true);
To read a slice of the state in a safe manner (e.g., avoiding race conditions), use readSafe. This method ensures the state is accessed while locking the pipeline.
store.getState('*', (state) => {
console.log('State object:', state);
});
Actions are directly bound to the store’s dispatch method, allowing you to invoke actions as regular functions without manually calling dispatch. This design keeps state update logic close to actions and enables a clean, intuitive API where calling an action immediately dispatches it to update state.
import { Action, action, featureSelector, selector } from '@actioncrew/actionstack';
export const addMessage = action("ADD_MESSAGE", (message: string) => ({ message }));
export const clearMessages = action('CLEAR_MESSAGES');
const featureModule = createModule({
slice: 'superModule',
actions: {
addMessage,
clearMessage
},
dependencies: { heroService: new HeroService() }
});
...
// Dispatching an action to add a message
featureModule.actions.addMessage("Hello, world!");
// Dispatching an action to add another message
featureModule.actions.addMessage("This is a second message!");
// Dispatching an action to clear all messages
featureModule.actions.clearMessages();
You can also subscribe to changes in the state, so that when messages are added or cleared, you can react to those changes:
import { Action, action, featureSelector, selector } from '@actioncrew/actionstack';
export const selectHeroes = selector(state => state.heroes);
const featureModule = createModule({
slice: 'superModule',
selectors: {
selectHeroes
},
dependencies: { heroService: new HeroService() }
});
...
// Subscribe to state changes
this.subscription = featureModule.data$.selectHeroes().subscribe(value => {
this.heroes = value;
});
You can combine multiple data streams from different feature modules or selectors as needed to create complex derived state or orchestrate side effects. Thanks to Streamix-powered reactive streams (data$), Actionstack lets you compose, transform, and react to state changes declaratively, enabling powerful and flexible reactive workflows across your application.
ActionStack makes state management in your applications easier, more predictable, and scalable. With support for both epics and sagas, it excels in handling asynchronous operations while offering the flexibility and power of Streamix and generator functions. Whether you're working on a small project or a large-scale application, ActionStack can help you manage state efficiently and reliably.
If you're interested, join our discussions on GitHub!
Have fun and happy coding!