Modules, Plugins & Pinia Migration
Namespaced Modules
const cartModule = {
namespaced: true, // required to avoid colliding with another module's
// action/mutation/getter of the same name
state: () => ({ items: [] }),
mutations: {
ADD_ITEM(state, item) { state.items.push(item); },
},
actions: {
async checkout({ state, commit }) {
await api.checkout(state.items);
commit('CLEAR');
},
},
};
const store = createStore({
modules: { cart: cartModule },
});
// Namespaced dispatch/commit needs the module prefix
store.dispatch('cart/checkout');
store.commit('cart/ADD_ITEM', item);Plugins
// A plugin receives the store and can subscribe to every mutation —
// used for logging, persistence, or custom devtools integrations
const localStoragePlugin = (store) => {
store.subscribe((mutation, state) => {
localStorage.setItem('app-state', JSON.stringify(state));
});
};
const store = createStore({
// ...
plugins: [localStoragePlugin],
});What Belongs in the Store
Reserve the store for genuinely shared state. Purely local, ephemeral UI state (is this dropdown open) usually belongs in a component's own data/ref instead — routing it through commit/dispatch adds indirection with no sharing benefit when nothing else needs that value.
Migrating to Pinia
// Pinia is the newer, officially recommended replacement — no separate
// "mutations" concept, actions can modify state directly
import { defineStore } from 'pinia';
export const useTodoStore = defineStore('todos', {
state: () => ({ todos: [], loading: false }),
getters: {
completedTodos: (state) => state.todos.filter((t) => t.completed),
},
actions: {
async fetchTodos() {
this.loading = true;
this.todos = await api.getTodos(); // direct mutation — no commit() needed
this.loading = false;
},
},
});Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free