State management pattern and library for Vue.js applications that serves as a centralized store with predictable state mutations
—
Modular organization system for complex applications with namespacing, dynamic registration, and hierarchical state management.
Define store modules with their own state, mutations, actions, and getters.
/**
* Module definition interface
*/
interface Module<S, R> {
/** Enable/disable namespacing for this module */
namespaced?: boolean;
/** Module state object or function returning state */
state?: S | (() => S);
/** Module getters for derived state */
getters?: GetterTree<S, R>;
/** Module actions for async operations */
actions?: ActionTree<S, R>;
/** Module mutations for state changes */
mutations?: MutationTree<S>;
/** Sub-modules for nested organization */
modules?: ModuleTree<R>;
}
interface GetterTree<S, R> {
[key: string]: Getter<S, R>;
}
interface ActionTree<S, R> {
[key: string]: ActionHandler<S, R>;
}
interface MutationTree<S> {
[key: string]: Mutation<S>;
}
interface ModuleTree<R> {
[key: string]: Module<any, R>;
}
type Getter<S, R> = (state: S, getters: any, rootState: R, rootGetters: any) => any;
type ActionHandler<S, R> = (context: ActionContext<S, R>, payload?: any) => any;
type Mutation<S> = (state: S, payload?: any) => any;Usage Examples:
// User module
const userModule = {
namespaced: true,
state: () => ({
profile: null,
preferences: {},
isAuthenticated: false
}),
mutations: {
setProfile(state, profile) {
state.profile = profile;
},
setAuthenticated(state, status) {
state.isAuthenticated = status;
}
},
actions: {
async login({ commit, dispatch }, credentials) {
const user = await api.login(credentials);
commit('setProfile', user);
commit('setAuthenticated', true);
// Call root action
dispatch('initializeApp', null, { root: true });
}
},
getters: {
displayName: state => state.profile?.name || 'Guest',
isLoggedIn: state => state.isAuthenticated && state.profile
}
};
// Cart module with sub-modules
const cartModule = {
namespaced: true,
state: () => ({
items: [],
discounts: []
}),
modules: {
shipping: shippingModule,
payment: paymentModule
},
mutations: {
addItem(state, item) {
const existing = state.items.find(i => i.id === item.id);
if (existing) {
existing.quantity += item.quantity;
} else {
state.items.push(item);
}
}
},
getters: {
total: state => state.items.reduce((sum, item) =>
sum + (item.price * item.quantity), 0),
itemCount: state => state.items.reduce((sum, item) =>
sum + item.quantity, 0)
}
};Register modules dynamically at runtime.
/**
* Register a module dynamically
* @param path - Module path (string or array)
* @param module - Module definition
* @param options - Registration options
*/
registerModule<T>(path: string | string[], module: Module<T, S>, options?: ModuleOptions): void;
interface ModuleOptions {
/** Preserve existing state when registering */
preserveState?: boolean;
}Usage Examples:
import { createStore } from 'vuex';
const store = createStore({
state: { count: 0 }
});
// Register single module
store.registerModule('user', userModule);
// Access: store.state.user.profile
// Commit: store.commit('user/setProfile', profile)
// Register nested module
store.registerModule(['cart', 'shipping'], shippingModule);
// Access: store.state.cart.shipping.address
// Commit: store.commit('cart/shipping/setAddress', address)
// Register with preserved state
store.registerModule('settings', settingsModule, {
preserveState: true // Don't replace existing settings state
});
// Register multiple modules
const modules = {
user: userModule,
cart: cartModule,
notifications: notificationModule
};
Object.keys(modules).forEach(name => {
store.registerModule(name, modules[name]);
});Unregister modules dynamically to clean up resources.
/**
* Unregister a module dynamically
* @param path - Module path (string or array)
*/
unregisterModule(path: string | string[]): void;Usage Examples:
// Unregister single module
store.unregisterModule('user');
// Unregister nested module
store.unregisterModule(['cart', 'shipping']);
// Conditional unregistration
if (store.hasModule('temporaryData')) {
store.unregisterModule('temporaryData');
}
// Clean up route-specific modules
router.beforeEach((to, from, next) => {
// Unregister previous route's modules
if (from.meta.storeModules) {
from.meta.storeModules.forEach(moduleName => {
if (store.hasModule(moduleName)) {
store.unregisterModule(moduleName);
}
});
}
// Register new route's modules
if (to.meta.storeModules) {
to.meta.storeModules.forEach(({ name, module }) => {
store.registerModule(name, module);
});
}
next();
});Check if a module is registered.
/**
* Check if a module exists
* @param path - Module path (string or array)
* @returns True if module exists
*/
hasModule(path: string | string[]): boolean;Usage Examples:
// Check single module
if (store.hasModule('user')) {
console.log('User module is registered');
}
// Check nested module
if (store.hasModule(['cart', 'payment'])) {
store.dispatch('cart/payment/processPayment');
}
// Conditional registration
if (!store.hasModule('analytics')) {
store.registerModule('analytics', analyticsModule);
}
// Guard against missing modules
const safeDispatch = (action, payload) => {
const [module] = action.split('/');
if (store.hasModule(module)) {
return store.dispatch(action, payload);
} else {
console.warn(`Module ${module} not found for action ${action}`);
return Promise.resolve();
}
};Access namespaced module state, getters, mutations, and actions.
Usage Examples:
const store = createStore({
modules: {
user: {
namespaced: true,
state: { profile: null },
mutations: { setProfile(state, profile) { state.profile = profile; } },
actions: {
async fetchProfile({ commit }, userId) {
const profile = await api.getProfile(userId);
commit('setProfile', profile);
}
},
getters: {
displayName: state => state.profile?.name || 'Guest'
}
},
cart: {
namespaced: true,
state: { items: [] },
modules: {
shipping: {
namespaced: true,
state: { address: null },
mutations: { setAddress(state, address) { state.address = address; } }
}
}
}
}
});
// Access namespaced state
const userProfile = store.state.user.profile;
const cartItems = store.state.cart.items;
const shippingAddress = store.state.cart.shipping.address;
// Access namespaced getters
const displayName = store.getters['user/displayName'];
// Commit namespaced mutations
store.commit('user/setProfile', profile);
store.commit('cart/shipping/setAddress', address);
// Dispatch namespaced actions
await store.dispatch('user/fetchProfile', userId);
// Using helpers with namespaces
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState('user', ['profile']),
...mapState('cart', {
cartItems: 'items'
})
},
methods: {
...mapActions('user', ['fetchProfile']),
...mapActions('cart', ['addItem'])
}
};Access root state and dispatch root actions from within modules.
Usage Examples:
const userModule = {
namespaced: true,
state: () => ({ profile: null }),
actions: {
async login({ commit, dispatch, rootState, rootGetters }, credentials) {
// Access root state
const appVersion = rootState.appVersion;
// Access root getters
const isOnline = rootGetters.isOnline;
if (!isOnline) {
throw new Error('Cannot login while offline');
}
const user = await api.login(credentials, { appVersion });
commit('setProfile', user);
// Dispatch root action
await dispatch('analytics/trackEvent', {
event: 'user_login',
userId: user.id
}, { root: true });
// Commit root mutation
commit('setLastLoginTime', Date.now(), { root: true });
}
},
getters: {
// Access root state in getters
canAccessPremium: (state, getters, rootState, rootGetters) => {
return state.profile?.tier === 'premium' || rootGetters.hasGlobalAccess;
}
}
};Support for hot module replacement during development.
Usage Examples:
import { createStore } from 'vuex';
import userModule from './modules/user';
import cartModule from './modules/cart';
const store = createStore({
modules: {
user: userModule,
cart: cartModule
}
});
// Hot module replacement
if (module.hot) {
// Accept updates for modules
module.hot.accept(['./modules/user'], () => {
const newUserModule = require('./modules/user').default;
store.hotUpdate({
modules: {
user: newUserModule
}
});
});
// Accept updates for multiple modules
module.hot.accept([
'./modules/user',
'./modules/cart'
], () => {
const newUserModule = require('./modules/user').default;
const newCartModule = require('./modules/cart').default;
store.hotUpdate({
modules: {
user: newUserModule,
cart: newCartModule
}
});
});
}
export default store;Advanced patterns for organizing and composing modules.
Usage Examples:
// Base module factory
const createCrudModule = (resource) => ({
namespaced: true,
state: () => ({
items: [],
loading: false,
error: null
}),
mutations: {
setItems(state, items) { state.items = items; },
setLoading(state, loading) { state.loading = loading; },
setError(state, error) { state.error = error; }
},
actions: {
async fetchAll({ commit }) {
commit('setLoading', true);
try {
const items = await api.getAll(resource);
commit('setItems', items);
} catch (error) {
commit('setError', error);
} finally {
commit('setLoading', false);
}
}
},
getters: {
byId: state => id => state.items.find(item => item.id === id),
count: state => state.items.length
}
});
// Create specific modules
const usersModule = {
...createCrudModule('users'),
actions: {
...createCrudModule('users').actions,
async login({ dispatch }, credentials) {
// User-specific action
const user = await api.login(credentials);
dispatch('fetchAll'); // Inherited action
return user;
}
}
};
const postsModule = createCrudModule('posts');
// Plugin-based module composition
const withCaching = (module, cacheKey) => ({
...module,
actions: {
...module.actions,
async fetchAll({ commit, state }) {
const cached = localStorage.getItem(cacheKey);
if (cached) {
commit('setItems', JSON.parse(cached));
} else {
await module.actions.fetchAll({ commit, state });
localStorage.setItem(cacheKey, JSON.stringify(state.items));
}
}
}
});
const cachedUsersModule = withCaching(usersModule, 'cached_users');Install with Tessl CLI
npx tessl i tessl/npm-vuex