How to Use Vuex Store
How to Use Vuex Store Vuex is the official state management pattern and library for Vue.js applications. It serves as a centralized store for all the components in an application, enabling predictable state changes and seamless data sharing across components—regardless of their hierarchy. As Vue applications grow in complexity, managing state through props and event emission becomes unwieldy and e
How to Use Vuex Store
Vuex is the official state management pattern and library for Vue.js applications. It serves as a centralized store for all the components in an application, enabling predictable state changes and seamless data sharing across componentsregardless of their hierarchy. As Vue applications grow in complexity, managing state through props and event emission becomes unwieldy and error-prone. Vuex solves this by providing a single source of truth, making your applications state easier to reason about, debug, and maintain.
Whether you're building a single-page application with dynamic user interfaces, real-time dashboards, or e-commerce platforms with shopping carts and authentication flows, Vuex ensures that your data remains consistent and responsive. It integrates seamlessly with Vues reactivity system and supports advanced features like middleware, plugins, and time-travel debugging via Vue DevTools.
This guide provides a comprehensive, step-by-step walkthrough of how to use Vuex Store effectivelyfrom installation to advanced patternsalong with best practices, real-world examples, and essential tools to help you master state management in Vue.js applications.
Step-by-Step Guide
1. Installing Vuex
Before you can use Vuex, you must install it in your Vue project. If youre using Vue 3, youll need Vuex 4, which is compatible with Vue 3s Composition API and supports both Options and Composition API styles.
To install Vuex via npm:
npm install vuex@next
Or if youre using yarn:
yarn add vuex@next
If youre working with Vue 2, install Vuex 3:
npm install vuex@3
Once installed, you need to create and configure the store. In your projects main JavaScript file (typically main.js or main.ts), import Vuex and create a store instance:
import { createApp } from 'vue'
import App from './App.vue'
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
count: 0
}
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
},
getters: {
doubleCount: state => state.count * 2
}
})
const app = createApp(App)
app.use(store)
app.mount('
app')
This creates a basic store with state, mutations, actions, and getters. The app.use(store) line registers the store globally, making it accessible to all components in the application.
2. Understanding the Core Concepts
Vuex is built around four core concepts: State, Getters, Mutations, and Actions. Each plays a distinct role in managing application state.
State
The state is the single source of truth. It holds the data that your application needs to render and function. In Vuex, state is stored as a plain JavaScript object and is reactiveany changes to it automatically trigger updates in components that depend on it.
Example:
state() {
return {
user: null,
isAuthenticated: false,
cartItems: []
}
}
Components can access state using the $store.state property or via the mapState helper.
Getters
Getters are computed properties for the store. They allow you to derive new state based on existing state. For example, you might want to calculate the total number of items in a shopping cart or filter a list of users by role.
Example:
getters: {
cartTotal: state => state.cartItems.reduce((total, item) => total + item.quantity, 0),
activeUsers: state => state.users.filter(user => user.isActive)
}
To use a getter in a component:
computed: {
...mapGetters(['cartTotal', 'activeUsers'])
}
Or access directly: this.$store.getters.cartTotal
Mutations
Mutations are the only way to change state in a Vuex store. They are synchronous functions that receive the state as the first argument and can optionally receive additional parameters (payloads).
Example:
mutations: {
setUser(state, user) {
state.user = user
state.isAuthenticated = true
},
addToCart(state, item) {
state.cartItems.push(item)
}
}
Mutations must be synchronous. If you need to perform asynchronous operations (like API calls), use actions instead.
To commit a mutation from a component:
this.$store.commit('setUser', userData)
this.$store.commit('addToCart', { id: 1, name: 'Laptop', price: 999 })
Actions
Actions are used to handle asynchronous operations and can commit mutations. They receive a context object that contains methods like commit, dispatch, and access to state and getters.
Example:
actions: {
async login({ commit }, credentials) {
try {
const response = await api.login(credentials)
commit('setUser', response.data.user)
localStorage.setItem('token', response.data.token)
} catch (error) {
commit('setUser', null)
throw error
}
},
fetchProducts({ commit }) {
return api.get('/products').then(response => {
commit('setProducts', response.data)
})
}
}
Actions are dispatched using dispatch:
this.$store.dispatch('login', { email: 'user@example.com', password: 'secret' })
Unlike mutations, actions can be asynchronous and can call other actions or mutations.
3. Creating a Modular Store
As applications grow, the store file can become large and difficult to maintain. Vuex supports modules, which allow you to split the store into smaller, reusable pieces.
Create a folder structure like this:
store/
??? index.js
??? auth.js
??? cart.js
??? products.js
Each module can have its own state, mutations, actions, and getters.
Example: store/auth.js
const authModule = {
namespaced: true,
state() {
return {
user: null,
token: localStorage.getItem('token') || null,
loading: false
}
},
mutations: {
setUser(state, user) {
state.user = user
},
setToken(state, token) {
state.token = token
},
setLoading(state, loading) {
state.loading = loading
}
},
actions: {
async login({ commit }, credentials) {
commit('setLoading', true)
try {
const response = await api.login(credentials)
commit('setUser', response.data.user)
commit('setToken', response.data.token)
localStorage.setItem('token', response.data.token)
} finally {
commit('setLoading', false)
}
},
logout({ commit }) {
commit('setUser', null)
commit('setToken', null)
localStorage.removeItem('token')
}
},
getters: {
isLoggedIn: state => !!state.token
}
}
export default authModule
Register modules in store/index.js:
import { createStore } from 'vuex'
import auth from './auth'
import cart from './cart'
import products from './products'
export default createStore({
modules: {
auth,
cart,
products
}
})
Accessing namespaced module state in components:
computed: {
...mapState('auth', ['user', 'loading']),
...mapGetters('auth', ['isLoggedIn']),
...mapState('cart', ['cartItems'])
}
Or use full path:
this.$store.state.auth.user
this.$store.getters['auth/isLoggedIn']
this.$store.dispatch('auth/login', credentials)
4. Using Vuex with the Composition API
If you're using Vue 3 with the Composition API, you can access the store using the useStore function from Vuex.
First, ensure your store is created as shown earlier. Then, in a component using setup():
import { defineComponent, computed } from 'vue'
import { useStore } from 'vuex'
export default defineComponent({
setup() {
const store = useStore()
const count = computed(() => store.state.count)
const doubleCount = computed(() => store.getters.doubleCount)
const increment = () => store.commit('increment')
const incrementAsync = () => store.dispatch('incrementAsync')
return {
count,
doubleCount,
increment,
incrementAsync
}
}
})
You can also create custom composables to encapsulate store logic:
// composables/useCart.js
import { useStore } from 'vuex'
import { computed } from 'vue'
export function useCart() {
const store = useStore()
const cartItems = computed(() => store.state.cart.items)
const cartTotal = computed(() => store.getters['cart/total'])
const addToCart = (item) => store.dispatch('cart/addItem', item)
const removeFromCart = (id) => store.dispatch('cart/removeItem', id)
return {
cartItems,
cartTotal,
addToCart,
removeFromCart
}
}
Then use it in any component:
import { useCart } from '@/composables/useCart'
export default {
setup() {
const { cartItems, cartTotal, addToCart } = useCart()
return {
cartItems,
cartTotal,
addToCart
}
}
}
5. Persisting State with Plugins
By default, Vuex state is lost when the page is refreshed. To persist state across sessions (e.g., user login status, cart items), you can use plugins.
One popular approach is to use vuex-persistedstate:
npm install vuex-persistedstate
Then configure it in your store:
import { createStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'
const store = createStore({
plugins: [createPersistedState()],
state: {
user: null,
cartItems: []
},
// ... mutations, actions, getters
})
This automatically saves the entire state to localStorage. You can also customize which modules to persist:
createPersistedState({
paths: ['auth', 'cart']
})
For more control, you can also use sessionStorage or encrypt data using libraries like crypto-js.
Best Practices
1. Keep State Minimal and Atomic
Only store data in Vuex that is genuinely shared across multiple components. Avoid putting every piece of local component state into the store. For example, a form inputs value should typically remain local to the component unless it needs to be accessed elsewhere.
Ask yourself: Will this data be used by more than one component, or is it only relevant to this one? If the answer is no, keep it local.
2. Use Namespaced Modules for Scalability
As your application grows, avoid putting everything into a single store file. Use namespaced modules to logically group related state, mutations, actions, and getters. This improves code organization, reduces naming collisions, and makes testing easier.
3. Always Use Mutations to Change State
Never modify state directly in components or actions. Always commit mutations. This ensures that state changes are tracked and traceable. Vuex DevTools rely on mutations to provide time-travel debugging.
4. Keep Mutations Synchronous
Mutations must be synchronous so that the state can be reliably tracked. If you need to perform async work (e.g., API calls), use actions. Actions can handle async logic and then commit mutations once the data is ready.
5. Use Actions for Side Effects
Actions are the right place for side effects: API calls, routing changes, local storage updates, etc. They should never directly mutate state. Instead, they should commit mutations to trigger state changes.
6. Avoid Deeply Nested State
Deeply nested state objects can make it harder to track changes and increase the risk of unintended mutations. Flatten your state structure where possible. For example:
Instead of:
state: {
user: {
profile: {
address: {
street: '',
city: '',
zip: ''
}
}
}
}
Consider:
state: {
user: {},
userAddress: {
street: '',
city: '',
zip: ''
}
}
7. Use Getters for Derived State
Use getters to compute derived values rather than doing the computation in components. This improves performance through memoization and keeps logic centralized.
8. Use TypeScript for Type Safety
If youre using Vue 3 with TypeScript, define interfaces for your state, mutations, and actions. This provides autocomplete, compile-time error checking, and better documentation.
Example:
interface State {
count: number
user: User | null
}
interface User {
id: number
name: string
email: string
}
const store = createStore({
state: {
count: 0,
user: null
},
mutations: {
increment(state) {
state.count++
},
setUser(state, user: User) {
state.user = user
}
}
})
9. Handle Errors Gracefully
Always wrap async actions in try-catch blocks to prevent unhandled promise rejections. Provide fallbacks or user feedback when API calls fail.
10. Test Your Store
Write unit tests for your store modules. Test mutations to ensure they modify state correctly. Test actions to verify they dispatch the right mutations or make correct API calls (using mocks).
Example with Jest:
import { createStore } from 'vuex'
import auth from '@/store/auth'
describe('auth module', () => {
let store
beforeEach(() => {
store = createStore({
modules: {
auth
}
})
})
test('sets user on login', () => {
store.dispatch('auth/login', { email: 'test@example.com', password: '123' })
expect(store.state.auth.user).toEqual({ id: 1, name: 'Test User' })
})
})
Tools and Resources
1. Vue DevTools
The Vuex plugin for Vue DevTools is essential for debugging. It allows you to:
- See the current state of your store
- Track every mutation and action
- Time-travel through state history
- Dispatch actions and commit mutations manually
Install it as a browser extension (Chrome, Firefox, Edge) from the official Vue DevTools repository.
2. Vuex-PersistedState
As mentioned earlier, this plugin automatically persists your Vuex state to localStorage or sessionStorage. Its lightweight and highly configurable.
GitHub: https://github.com/robinvdvleuten/vuex-persistedstate
3. Pinia (Alternative to Vuex)
While this guide focuses on Vuex, its worth noting that Pinia is the new recommended state management library for Vue 3. Its simpler, more intuitive, and fully compatible with the Composition API. If youre starting a new Vue 3 project, consider Pinia. However, Vuex remains widely used and fully supported in legacy and large-scale applications.
Pinia Documentation: https://pinia.vuejs.org/
4. Vue CLI and Vite
Both Vue CLI and Vite support Vuex out of the box. When creating a new project, you can choose to include Vuex during setup:
npm create vue@latest
Follow the prompts and select Vuex when asked about state management.
5. ESLint and Vuex Plugin
Use eslint-plugin-vue with the vue/require-store-in-computed rule to ensure youre accessing the store correctly in computed properties. This helps prevent common mistakes like accessing $store directly in templates.
6. Documentation and Learning Resources
- Official Vuex Documentation: https://vuex.vuejs.org/
- Vue Mastery Vuex Course: Comprehensive video tutorials on state management
- Vue School: Free and paid courses on Vuex and Pinia
- GitHub Repositories: Explore open-source Vue apps using Vuex to see real-world patterns
Real Examples
Example 1: Authentication Flow
Lets walk through a complete authentication system using Vuex.
store/auth.js
import api from '@/api'
const authModule = {
namespaced: true,
state() {
return {
user: null,
token: localStorage.getItem('token') || null,
loading: false,
error: null
}
},
mutations: {
setUser(state, user) {
state.user = user
state.error = null
},
setToken(state, token) {
state.token = token
},
setLoading(state, loading) {
state.loading = loading
},
setError(state, error) {
state.error = error
},
logout(state) {
state.user = null
state.token = null
state.error = null
}
},
actions: {
async login({ commit }, credentials) {
commit('setLoading', true)
commit('setError', null)
try {
const response = await api.post('/login', credentials)
const { user, token } = response.data
commit('setUser', user)
commit('setToken', token)
localStorage.setItem('token', token)
} catch (error) {
commit('setError', error.response?.data?.message || 'Login failed')
throw error
} finally {
commit('setLoading', false)
}
},
async register({ commit }, userData) {
commit('setLoading', true)
try {
const response = await api.post('/register', userData)
const { user, token } = response.data
commit('setUser', user)
commit('setToken', token)
localStorage.setItem('token', token)
} catch (error) {
commit('setError', error.response?.data?.message || 'Registration failed')
throw error
} finally {
commit('setLoading', false)
}
},
logout({ commit }) {
commit('logout')
localStorage.removeItem('token')
},
checkAuth({ commit }) {
const token = localStorage.getItem('token')
if (token) {
commit('setToken', token)
// Optionally fetch user data from API
// api.get('/me').then(response => commit('setUser', response.data))
}
}
},
getters: {
isLoggedIn: state => !!state.token,
user: state => state.user,
loading: state => state.loading,
error: state => state.error
}
}
export default authModule
Component: Login.vue
<template>
<div>
<h2>Login</h2>
<form @submit.prevent="handleLogin">
<input v-model="email" type="email" placeholder="Email" required />
<input v-model="password" type="password" placeholder="Password" required />
<button type="submit" :disabled="loading">
{{ loading ? 'Logging in...' : 'Login' }}
</button>
</form>
<p v-if="error" class="error">{{ error }}</p>
</div>
</template>
<script>
import { computed } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
export default {
setup() {
const store = useStore()
const router = useRouter()
const email = ref('')
const password = ref('')
const loading = computed(() => store.state.auth.loading)
const error = computed(() => store.state.auth.error)
const handleLogin = async () => {
try {
await store.dispatch('auth/login', { email: email.value, password: password.value })
router.push('/dashboard')
} catch (err) {
console.error(err)
}
}
return {
email,
password,
loading,
error,
handleLogin
}
}
}
</script>
Example 2: Shopping Cart
store/cart.js
const cartModule = {
namespaced: true,
state() {
return {
items: []
}
},
mutations: {
addItem(state, item) {
const existing = state.items.find(i => i.id === item.id)
if (existing) {
existing.quantity += 1
} else {
state.items.push({ ...item, quantity: 1 })
}
},
removeItem(state, id) {
state.items = state.items.filter(item => item.id !== id)
},
updateQuantity(state, { id, quantity }) {
const item = state.items.find(i => i.id === id)
if (item) {
item.quantity = Math.max(0, quantity)
if (item.quantity === 0) {
this.removeItem(state, id)
}
}
},
clearCart(state) {
state.items = []
}
},
actions: {
addToCart({ commit }, item) {
commit('addItem', item)
},
removeFromCart({ commit }, id) {
commit('removeItem', id)
},
updateItemQuantity({ commit }, payload) {
commit('updateQuantity', payload)
},
clearCart({ commit }) {
commit('clearCart')
}
},
getters: {
totalItems: state => state.items.reduce((sum, item) => sum + item.quantity, 0),
totalPrice: state => state.items.reduce((sum, item) => sum + item.price * item.quantity, 0),
items: state => state.items
}
}
export default cartModule
Component: ProductCard.vue
<template>
<div>
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
<button @click="addToCart">Add to Cart</button>
</div>
</template>
<script>
import { defineComponent } from 'vue'
import { useStore } from 'vuex'
export default defineComponent({
props: ['product'],
setup(props) {
const store = useStore()
const addToCart = () => {
store.dispatch('cart/addToCart', props.product)
}
return {
addToCart
}
}
})
</script>
FAQs
Is Vuex still relevant with the release of Pinia?
Yes. While Pinia is the recommended choice for new Vue 3 projects due to its simplicity and Composition API integration, Vuex remains widely used in existing applications and is fully supported. Many enterprise applications still rely on Vuex, and it continues to be maintained by the Vue team.
Can I use Vuex without Vue CLI?
Absolutely. Vuex works with any Vue project setup, including Vite, Webpack, or even CDN-based Vue. As long as you import and register the store correctly, it will function as expected.
Do I need to use modules in small applications?
No. For small applications with minimal state, a single store file is perfectly acceptable. Modules are for scalability. Start simple and refactor into modules as your app grows.
How do I handle global loading states?
Create a dedicated module like ui or app to manage UI-related state such as loading, toast messages, or modals. This keeps UI state separate from data state.
Can I use Vuex with SSR (Server-Side Rendering)?
Yes. Vuex supports SSR. You must create a new store instance for each request on the server and hydrate it on the client. The official Vue SSR documentation includes detailed examples for Vuex integration.
How do I reset the Vuex store on logout?
Instead of destroying the store, create a mutation that resets state to its initial values. You can use a factory function to generate the initial state and call it during logout:
const initialState = () => ({
user: null,
token: null
})
const store = createStore({
state: initialState(),
mutations: {
resetState(state) {
const s = initialState()
Object.keys(s).forEach(key => {
state[key] = s[key]
})
}
},
actions: {
logout({ commit }) {
commit('resetState')
localStorage.removeItem('token')
}
}
})
Whats the difference between mapState and computed properties?
mapState is a helper that automatically generates computed properties from the store. It reduces boilerplate but doesnt replace computed properties. You can still use regular computed properties alongside mapState for derived logic.
Conclusion
Vuex Store is a powerful, battle-tested solution for managing global state in Vue.js applications. By centralizing state, enforcing predictable mutations, and supporting asynchronous actions, Vuex brings structure and clarity to complex UIs. Whether youre building a small app or a large-scale enterprise platform, understanding how to use Vuex effectively is essential for scalable, maintainable code.
This guide walked you through installation, core concepts, modular design, Composition API integration, persistence, best practices, real-world examples, and common questions. You now have the knowledge to implement Vuex confidently in your projects.
Remember: Vuex isnt always necessary. Use it when you need shared state across components. Keep local state local. Prioritize simplicity. And as your application evolves, dont hesitate to refactorVuex is designed to grow with you.
For further learning, explore the official documentation, experiment with real projects, and consider transitioning to Pinia if youre starting fresh with Vue 3. But for nowmaster Vuex, and youll master state management in Vue.