Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 17 Beginner

Vuex Fundamentals

Updated: May 18, 2026
5 min read

# CHAPTER 17

Vuex Fundamentals

1. Chapter Introduction

Vuex was the original official state management library for Vue. While Pinia is the modern standard for new Vue 3 projects, Vuex remains widely used in Vue 2 codebases and older Vue 3 projects. Understanding Vuex is essential for maintaining legacy code and interviews.

2. Learning Objectives

  • Understand Vuex core concepts: state, mutations, actions, getters.
  • Use Vuex in a Vue 3 project.
  • Understand why Vuex requires mutations.
  • Compare Vuex and Pinia.
  • Organize large stores with modules.

3. Vuex vs Pinia Comparison

text
123456789101112
Feature       │ Vuex 4               │ Pinia
─────────────────────────────────────────────────
State         │ state: {}            │ ref() / reactive()
Read State    │ store.state.count    │ store.count
Change State  │ Only via mutations   │ Directly or via actions
Mutations     │ Required             │ Not needed
Actions       │ For async ops        │ Any function (sync/async)
Getters       │ Separate getters: {} │ computed()
Modules       │ Complex setup        │ Separate store files
TypeScript    │ Difficult            │ Excellent
DevTools      │ Supported            │ Supported
Learning Curve│ Steep                │ Gentle

4. Setting Up Vuex

bash
1
npm install vuex@4
javascript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
// src/store/index.js — Vuex store
import { createStore } from 'vuex'

export const store = createStore({

  // STATE: Single source of truth
  state() {
    return {
      count: 0,
      user: null,
      products: [],
      cart: [],
      loading: false,
      error: null
    }
  },

  // GETTERS: Computed values from state
  getters: {
    doubleCount: (state) => state.count * 2,
    cartTotal: (state) => state.cart.reduce((sum, item) => sum + item.price * item.qty, 0),
    cartItemCount: (state) => state.cart.reduce((sum, item) => sum + item.qty, 0),
    isLoggedIn: (state) => !!state.user,
    activeProducts: (state) => state.products.filter(p => p.active),
    // Getter that returns a function (for parameterized getters)
    productById: (state) => (id) => state.products.find(p => p.id === id)
  },

  // MUTATIONS: Synchronous state changes (ONLY way to change state)
  mutations: {
    INCREMENT(state) {
      state.count++
    },
    SET_COUNT(state, payload) {
      state.count = payload
    },
    SET_USER(state, user) {
      state.user = user
    },
    SET_PRODUCTS(state, products) {
      state.products = products
    },
    SET_LOADING(state, loading) {
      state.loading = loading
    },
    SET_ERROR(state, error) {
      state.error = error
    },
    ADD_TO_CART(state, product) {
      const existing = state.cart.find(i => i.id === product.id)
      if (existing) existing.qty++
      else state.cart.push({ ...product, qty: 1 })
    },
    REMOVE_FROM_CART(state, productId) {
      state.cart = state.cart.filter(i => i.id !== productId)
    }
  },

  // ACTIONS: Can be async. Call mutations to change state.
  actions: {
    async fetchProducts({ commit }) {
      commit('SET_LOADING', true)
      commit('SET_ERROR', null)
      try {
        const res = await fetch('/api/products')
        const products = await res.json()
        commit('SET_PRODUCTS', products)
      } catch (error) {
        commit('SET_ERROR', error.message)
      } finally {
        commit('SET_LOADING', false)
      }
    },

    async login({ commit }, credentials) {
      const res = await fetch('/api/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
        headers: { 'Content-Type': 'application/json' }
      })
      const user = await res.json()
      commit('SET_USER', user)
      return user
    },

    addToCart({ commit }, product) {
      commit('ADD_TO_CART', product)
    }
  }
})
main.js
12345
import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store'

createApp(App).use(store).mount('#app')

5. Using Vuex in Components

vue
123456789101112131415161718192021222324252627282930313233343536373839
<script setup>
import { computed } from &#039;vue'
import { useStore } from &#039;vuex'

const store = useStore()

// Reading state
const count = computed(() => store.state.count)
const user = computed(() => store.state.user)

// Using getters
const doubleCount = computed(() => store.getters.doubleCount)
const cartTotal = computed(() => store.getters.cartTotal)
const getProduct = (id) => store.getters.productById(id)

// Committing mutations (sync state changes)
function increment() {
  store.commit(&#039;INCREMENT')
}

function setCount(value) {
  store.commit(&#039;SET_COUNT', value)
}

// Dispatching actions (async operations)
async function loadProducts() {
  await store.dispatch(&#039;fetchProducts')
}

async function login(credentials) {
  await store.dispatch(&#039;login', credentials)
}
</script>

<template>
  <p>Count: {{ count }} (double: {{ doubleCount }})</p>
  <button @click="increment">+</button>
  <button @click="loadProducts">Load Products</button>
</template>

6. Vuex Modules

javascript
12345678910111213141516171819202122232425262728293031323334
// store/modules/auth.js
const authModule = {
  namespaced: true,  // Access with 'auth/login' etc.
  state: () => ({ user: null, token: null }),
  getters: {
    isLoggedIn: (state) => !!state.user
  },
  mutations: {
    SET_USER(state, user) { state.user = user },
    SET_TOKEN(state, token) { state.token = token },
    LOGOUT(state) { state.user = null; state.token = null }
  },
  actions: {
    async login({ commit }, credentials) { /* ... */ },
    logout({ commit }) { commit(&#039;LOGOUT&#039;) }
  }
}

// store/index.js with modules
import { createStore } from &#039;vuex&#039;
import authModule from &#039;./modules/auth&#039;
import cartModule from &#039;./modules/cart&#039;

export const store = createStore({
  modules: {
    auth: authModule,
    cart: cartModule
  }
})

// In component with namespaced module:
store.getters[&#039;auth/isLoggedIn&#039;]
store.commit(&#039;auth/SET_USER&#039;, user)
store.dispatch(&#039;auth/login&#039;, credentials)

7. Common Mistakes

  • Directly mutating state: store.state.count++ bypasses Vuex's tracking and breaks DevTools. Always use mutations.
  • Async code in mutations: Mutations must be synchronous. Use actions for API calls.

8. MCQs

Question 1

Only way to change Vuex state?

Question 2

Mutations must be?

Question 3

Actions are called via?

Question 4

store.getters.cartTotal is like?

Question 5

Vuex modules use namespaced: true for?

Question 6

useStore() in Composition API?

Question 7

Why Vuex requires mutations (not direct state mutation)?

Question 8

Getters with params (parameterized)?

Question 9

Accessing namespaced action?

Question 10

Pinia over Vuex for new projects because?

9. Interview Questions

  • Q: Why does Vuex require mutations for state changes instead of allowing direct mutations?
  • Q: What is the difference between mutations and actions in Vuex?

10. Summary

Vuex uses a strict flux-like pattern: state → getters (read), mutations (sync change), actions (async → commit mutations). While Pinia supersedes Vuex for new Vue 3 projects, Vuex knowledge is essential for maintaining existing codebases and understanding Vue's state management evolution.

11. Next Chapter Recommendation

In Chapter 18: API Integration and Axios, we connect Vue applications to real backends — making GET/POST requests with Axios, handling loading/error states, and building a weather app.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·