MAD Study Hub

Home / MAD2 / Week 7

Week 7 · State management with Vuex

Official syllabus line: Advanced State Management
About 90 minQuiz 2 material (see exam radar)BSCS2006 (+ project BSCS2006P) · Thejesh G N, Nitin Chandrachoodan
Before you start: MAD2 weeks 1-6 (Vue 2 components, props, events, computed, Vue Router, fetch calls to a Flask API).

The one-minute story

Think of it like this

Vuex is the school notice board and the office clerk. Imagine a school where every class keeps its own copy of the timetable. When the principal changes a period, 30 classes must be told, one by one, and some will be told wrong. So the school puts one timetable on one notice board (the state). Anyone can read it (getters are like a ready-made "today's free periods" summary). But nobody scribbles on the board directly. To change it you fill a request slip (an action or a commit) and give it to the clerk (a mutation), who is the only person allowed to hold the pen. Because every change passes through the clerk, you can keep a log of who changed what, and replay it.

Vuex is that notice board for a Vue app: one shared store, read from anywhere, changed only by mutations.

By the end of this week you can:

  • explain why shared state is needed and why prop drilling and an event bus break down;
  • write a Vuex 3 store with state, getters, mutations, actions and modules;
  • predict the output of code that mixes commit, dispatch, async actions and getters;
  • use this.$store, mapState, mapGetters, mapMutations and mapActions correctly;
  • keep an auth token in the store and survive a page refresh with localStorage;
  • say when not to use Vuex, and what Pinia is.

Words you need

Words you need
  • State - the data the whole app shares (cart items, logged-in user, token).
  • Store - the single object that holds state, getters, mutations and actions.
  • Getter - a read-only computed value derived from state (like a computed property for the store).
  • Mutation - a plain, synchronous function that changes the state. The only legal way to change it.
  • Action - a function that may be asynchronous (API calls, timers). It never changes state itself: it commits mutations.
  • commit - the call that runs a mutation: store.commit('name', payload).
  • dispatch - the call that runs an action: store.dispatch('name', payload).
  • Payload - the one extra argument you pass to a mutation or action. Need two values? Pass one object.
  • Module - a slice of the store with its own state, getters, mutations and actions. Namespaced means its names get a prefix like cart/.
  • Prop drilling - passing a prop through many middle components that do not need it, just to reach a deep child.
  • Event bus - an empty Vue instance used as a global message channel ($emit / $on).
  • Strict mode - a development setting that complains if state is changed outside a mutation.
  • Persistence - keeping data after a page refresh, for example in localStorage.
  • Reactivity - Vue noticing a data change and re-rendering by itself.

Idea 1: Why shared state is needed

In a small Vue app each component owns its data. Two components that need the same data can talk through props down and events up. That is fine for parent and child. It breaks when many far-apart components need the same thing: the logged-in user, the cart, the token, the notification count.

ApproachWhat goes wrong
Prop drillingApp passes user to Layout, which passes it to Sidebar, which passes it to Avatar. Layout and Sidebar never use it. Every rename touches every level.
Event bus (new Vue() as a channel)Anyone can emit, anyone can listen. Who changed the value? Who is listening? No central record, no devtools history, listeners leak if you forget $off.
Global variableChanging it does not re-render components (not reactive). Nothing tracks who changed it.
Vuex storeOne reactive source of truth. Every change is a named mutation that devtools can list and replay.
The idea

State management rule. Data used by one component stays in that component (data()). Data used by many components, or that must survive navigation between routes, goes in the store. Vuex gives a single source of truth: components read from it and request changes; they do not own copies.

Short-lived UI bits (a dropdown open or closed, text half-typed in a field) are called ephemeral state. Keep them local. Putting everything in Vuex is a common over-engineering mistake (see Idea 8).

Idea 2: The one-way data flow

Vuex is built on a loop where data moves in one direction only:

Component --dispatch--> Action --commit--> Mutation --mutates--> State --renders--> Component
 (user click)        (async work,        (sync, the only       (reactive)         (view updates)
                      API calls)          writer of state)

Components never write to state. The only road to state is commit a mutation. The road to an async job is dispatch an action, which later commits.

The idea

Order for async work: dispatch an action, then the action commits a mutation, then the state changes. Wrong orders such as "mutation, then action, then state" or "state, then dispatch" are false. For a simple synchronous change you may commit straight from the component and skip the action.

Idea 3: A complete store, line by line

Vue 2 + Vuex 3 loaded from a CDN, store injected into the root instance:

const store = new Vuex.Store({
  state: {
    todos: [{ text: 'Learn Vuex', done: true }, { text: 'Write app', done: false }],
    count: 0
  },
  getters: {
    doneCount(state) {                       // like a computed property
      return state.todos.filter(t => t.done).length
    },
    summary(state, getters) {                // getters can use other getters
      return getters.doneCount + ' of ' + state.todos.length + ' done'
    }
  },
  mutations: {
    increment(state, n = 1) {                // (state, payload)
      state.count += n
    },
    addTodo(state, todo) {
      state.todos.push(todo)
    }
  },
  actions: {
    async incrementLater(context, n) {       // context has commit, dispatch, state, getters
      await new Promise(resolve => setTimeout(resolve, 1000))
      context.commit('increment', n)
    }
  }
})

new Vue({ el: '#app', store })               // injects store as this.$store everywhere

What each part receives:

PartSignatureCan do
mutation(state, payload)change state, synchronously
getter(state, getters)read, return a derived value
action(context, payload)context.commit, context.dispatch, read context.state; may be async
Solved example: 1. Predict the output

Using the store above (with strict: false):

store.commit('increment')            // payload missing, default n = 1
store.commit('increment', 5)
console.log(store.state.count)       // 6
console.log(store.getters.doneCount) // 1
console.log(store.getters.summary)   // "1 of 2 done"

const p = store.dispatch('incrementLater', 10)
console.log(store.state.count)       // still 6, the action is waiting 1 second
p.then(() => console.log(store.state.count)) // 16 after one second

dispatch always returns a Promise, even if your action is not declared async. The log right after dispatch shows the old value because the timer has not fired. I ran this against Vuex 3.

The payload rule: one value only

commit('name', a, b) passes only a to the mutation. The second extra value is ignored. This is a classic exam trap.

// WRONG: price is lost
this.$store.commit('update_total_cost', count, price)

// RIGHT: send ONE object
this.$store.commit('update_total_cost', { count: count, price: price })
// mutation:
update_total_cost(state, payload) {
  state.total_cost = payload.count * payload.price
}

Also note the object-style commit: store.commit({ type: 'increment', n: 1 }) passes the whole object (including type) as the payload. With increment(state, n) that gives state.count += {type..., n...}, which is garbage (string concatenation). Use it only when the mutation reads payload.n.

Context versus this.$store

Inside an action the store is reached through its first argument: context.commit(...) or the destructured { commit }. Inside a component it is this.$store.commit(...). Mixing them up is the standard wrong option in "fill code1 and code2" questions: this.$store is not available inside a store action, and context does not exist in a component method.

Solved example: 2. Awardees: the question pattern the papers keep repeating

Fill code1 (inside an action) and code2 (inside the mutation update_final) so that awardees gets the companies that have a website and capital above 40000, plus a token sent from the action.

actions: {
  send_task(context) {
    const token = Math.random().toString(36).slice(2)
    context.commit('update_final', { min: 40000, token: token })   // code1
  }
},
mutations: {
  update_final(state, payload) {                                   // code2
    state.companies.forEach(company => {
      if (company.website != null && company.capital > payload.min) {
        state.awardees.push({ name: company.name, token: payload.token })
      }
    })
  }
}

Reject wrong options by these rules: (1) in an action use context.commit, never this.$store.commit; (2) pass one payload object, never commit('x', 40000, token); (3) inside a mutation reach data as state.companies, never bare companies or capital; (4) push into state.awardees, not a local name; (5) read payload fields as payload.min, payload.token.

Idea 4: Mutations are sync, actions may be async

The idea

Why mutations must be synchronous. Devtools records state before and after each mutation, so you can time-travel. If a mutation changes state later inside a timer or a fetch callback, devtools cannot tell which mutation caused the change. So: mutations = synchronous, directly modify state. Actions = may be asynchronous and call APIs; they change state only by committing mutations.

// GOOD mutation
ADD_TO_CART(state, product) { state.cart.push(product) }

// BAD mutations: async code inside
ADD_TO_CART(state, product) { setTimeout(() => state.cart.push(product), 500) }
ADD_TO_CART(state, product) { fetch('/api/add').then(() => state.cart.push(product)) }
ADD_TO_CART(state, product) { Promise.resolve().then(() => state.cart.push(product)) }

Even the third one, which looks instant, is async: then runs after the current code finishes.

Solved example: 3. Three ways to change the cart
state: { cart: [] },
mutations: { ADD_TO_CART(state, product) { state.cart.push(product) } },
actions: {
  addToCart({ commit }, product) {
    setTimeout(() => commit('ADD_TO_CART', product), 500)
  }
}
addItem_A(p) { this.$store.commit('ADD_TO_CART', p) },    // sync, in devtools immediately
addItem_B(p) { this.$store.dispatch('addToCart', p) },    // in devtools after 500 ms
addItem_C(p) { this.$store.state.cart.push(p) }           // bypasses mutations

All three change the cart (C works in non-strict mode because Vue still sees the array change). But devtools mutation history shows only A (at once) and B (with a delay). C is invisible in history. In strict mode C logs an error. This is almost word for word a real Quiz 2 question.

Dispatch and the Promise

An action returns a Promise, so a component can wait for it:

actions: {
  async addTodoAsync({ commit }, text) {
    commit('addTodo', { text, done: false })   // runs right now, before the first await
    return 'done'
  }
}
store.dispatch('addTodoAsync', 'Learn Vuex').then(msg => console.log(msg))
console.log(store.state.todos.length)

Output: the length prints first (the commit already ran synchronously inside the async function, so it prints the new length, for example 1 if the list was empty), then done. Code in an async function runs synchronously until the first await; the .then always runs later.

Idea 5: Using the store from components

Vue.component('score-display', {
  template: `
    <div>
      <p>Current Score: {{ score }}</p>
      <button @click="boostNow">Boost Now</button>
      <button @click="boostLater">Boost After 1s</button>
    </div>`,
  computed: {
    score() { return this.$store.state.score }       // read through a computed
  },
  methods: {
    boostNow()   { this.$store.commit('increaseScore') },
    boostLater() { this.$store.dispatch('increaseScoreAsync') }
  }
})
  • Read state in a computed property (so the view updates). Do not copy it once into data(): the copy would not follow later changes.
  • this.$store.increaseScoreAsync() is wrong: actions are not methods of the store; use dispatch. this.$vuexstore does not exist; the name is $store.
  • Every component in the tree gets $store once the root Vue instance has store (not only direct children).

The map helpers

They generate the repetitive code. Import them first: import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'.

computed: {
  ...mapState(['count', 'todos']),            // this.count, this.todos
  ...mapState({ n: 'count' }),                // renamed: this.n
  ...mapGetters(['doneCount', 'summary'])     // this.doneCount
},
methods: {
  ...mapMutations(['increment']),             // this.increment(5) means this.$store.commit('increment', 5)
  ...mapActions(['incrementLater'])           // this.incrementLater(10) means dispatch
}
HelperGoes intoReplaces
mapState, mapGetterscomputedthis.$store.state.x, this.$store.getters.x
mapMutations, mapActionsmethodsthis.$store.commit(...), this.$store.dispatch(...)

The spread ... is required when you also have your own computed properties: mapState([...]) alone returns an object, and without the spread it would replace the whole computed. A real question: the correct code is computed: { ...mapState(['stateA','stateB','stateC']) }. mapState expects the names that exist in the store state.

Your turn: 1. Fill the component

The store has state.score and the mutation increaseScore. Write a component computed property and a method using the map helpers.

Need a hint?

Computed uses mapState, methods use mapMutations.

Show the full solution
import { mapState, mapMutations } from 'vuex'
export default {
  computed: { ...mapState(['score']) },
  methods:  { ...mapMutations(['increaseScore']) }   // this.increaseScore() commits it
}

Idea 6: Getters, reactivity and the evidence-style trace

Getters are cached computed values of the store: they recompute only when the state they read changes. They are reactive, so the claim "getters are not reactive" is false.

Solved example: 4. Trace a getter after a mutation
state: { users: [
  { id: 1, name: 'Alice',   status: 'active',   role: 'admin' },
  { id: 2, name: 'Bob',     status: 'inactive', role: 'user'  },
  { id: 3, name: 'Charlie', status: 'active',   role: 'user'  } ] },
getters: {
  activeUsers(state) { return state.users.filter(u => u.status === 'active') },
  activeUserCount(state, getters) { return getters.activeUsers.length },
  adminUsers(state) { return state.users.filter(u => u.role === 'admin') }
},
mutations: {
  updateUserStatus(state, { userId, status }) {
    const user = state.users.find(u => u.id === userId)
    if (user) user.status = status
  }
}

Now store.commit('updateUserStatus', { userId: 2, status: 'active' }).

  • activeUsers now holds Alice, Bob, Charlie.
  • activeUserCount returns 3 (it was 2).
  • adminUsers is unchanged: only Alice.

If an action toggles users asynchronously (for example after a 1 second delay each), the count changes only when each delayed commit runs: it becomes 1 after the first completes and 2 after the second. A getter always shows the state at that moment; it never "runs ahead".

A getter with arguments returns a function: byDone: state => d => state.todos.filter(t => t.done === d), used as store.getters.byDone(false). Such getters are not cached.

Reactivity gotchas in Vue 2

Vue 2 cannot see two kinds of change. I tested both:

// not detected: no re-render
state.user.age = 9          // adding a NEW property to an object
state.list[0] = 100         // setting an array item by index

// detected
Vue.set(state.user, 'age', 9)          // or this.$set(...)
state.list.splice(0, 1, 100)
state.user = { ...state.user, age: 9 } // replace the whole object

Declare every property in the initial state, even as null, and Vue tracks it from the start. Array methods like push, splice, pop are fine. (Vue 3 and Pinia use Proxies and do not have this gotcha.)

Idea 7: Modules, namespacing, strict mode

As the app grows, split the store into modules:

const cart = {
  namespaced: true,
  state: () => ({ items: [] }),
  getters: { count: s => s.items.length },
  mutations: { add(s, item) { s.items.push(item) } },
  actions: { addLater({ commit }, item) { commit('add', item) } }
}
const store = new Vuex.Store({ modules: { cart } })

store.commit('cart/add', 'pen')
store.dispatch('cart/addLater', 'ink')
console.log(store.state.cart.items)       // [ 'pen', 'ink' ]
console.log(store.getters['cart/count'])  // 2
  • Module state is nested: store.state.cart.items.
  • Without namespaced: true, mutations and actions of all modules share one global name space (names can clash). With it you add the prefix: 'cart/add', and getters['cart/count'] (bracket access, because of the slash).
  • Module state should be a function if the module may be reused.
  • Map helpers take a namespace first: ...mapState('cart', ['items']), ...mapActions('cart', ['addLater']).

Strict mode: new Vuex.Store({ strict: true, ... }). If anything changes state outside a mutation (for example this.$store.state.count = 99), Vuex logs the error do not mutate vuex store state outside mutation handlers. Use it in development only; it has a performance cost, so turn it off in production (strict: process.env.NODE_ENV !== 'production').

Idea 8: API calls, the token, persistence, and when not to use Vuex

Call APIs from actions, then commit the result:

actions: {
  async login({ commit }, creds) {
    const res = await fetch('http://localhost:5000/login?include_auth_token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(creds)
    })
    if (!res.ok) throw new Error('Login failed')       // component can catch it
    const data = await res.json()
    commit('setToken', data.response.user.authentication_token)
  },
  async fetchValue({ commit }) {
    const value = await new Promise(r => setTimeout(() => r('API Value'), 500))
    commit('setValue', value)
  }
}

Only actions should hold fetch. Mutations only assign the already-fetched data to state. In the real paper, a component does await this.$store.dispatch('fetchValue') and after 500 ms the page shows API Value.

Auth token in the store and the page refresh problem

State lives in JavaScript memory. A refresh reloads the page and the memory is wiped, so the token vanishes and the user is logged out. Fix: keep a copy in web storage and load it when the store is created.

const store = new Vuex.Store({
  state: {
    token: sessionStorage.getItem('authToken') || null     // initial value from storage
  },
  mutations: {
    setToken(state, token) {
      state.token = token
      sessionStorage.setItem('authToken', token)           // keep a copy
    },
    clearToken(state) {
      state.token = null
      sessionStorage.removeItem('authToken')
    }
  },
  actions: {
    login({ commit }, token) { commit('setToken', token) },
    logout({ commit }) { commit('clearToken') }
  }
})

After login and a refresh, the store starts again, reads sessionStorage, and the token is back in state.token. Storage facts the papers test:

StorageLifetimeShared across tabsSent to server automatically
Vuex state / JS variableuntil refreshnono
sessionStorageuntil that tab closes (survives refresh)nono
localStorageuntil deleted (survives browser restart)yesno
cookieuntil expiry; session cookie until browser closesyesyes, on every request

Both web storages hold strings as key-value pairs: store objects with JSON.stringify and read with JSON.parse. Tradeoffs of storing tokens are in MAD2 week 8.

Watch out - real exam trap
  • Persisting only in the store does not survive refresh; the initial state must read from storage.
  • localStorage is not cleared on refresh, and not "synchronised across devices". sessionStorage is not shared across tabs.
  • In a Vuex store, the mutation (not the component) is the right place to write storage, so the two copies never drift apart.

When NOT to use Vuex

  • Small app or a page with one or two components: props and events are enough.
  • Data used by one component (form fields, open/closed menus): local data().
  • Data that is just a copy of the server and can be re-fetched: fetch it where needed; a cache layer is simpler.
  • Using Vuex is not always hard when apps grow; the opposite: it exists to keep large apps manageable. A component can still have its own local state when Vuex is used.
The idea

In newer Vue (Pinia). Vue 3 recommends Pinia, the successor of Vuex. It drops mutations (you change state in actions, or directly), has no modules (each store is its own file), and works with TypeScript and Proxies. The ideas you learn here (single source of truth, getters, actions) carry over. This course exam uses Vue 2 + Vuex 3, so write Vuex in answers.

In your project

Your project almost certainly needs a token and user role in the store. Examiner questions: "What happens on refresh?" (show the sessionStorage or localStorage restore), "Where is the API call made?" (an action), "Why a mutation to set the token?" (devtools history, single writer). Keep everything else local unless two far-apart components need it. Also be ready to say that hiding a button by role in Vue is a convenience: the Flask API must check the role too.

Your turn: 2. True or false

(a) A mutation may contain a fetch call. (b) this.$store.dispatch('x') returns a Promise. (c) Getters are not reactive. (d) Only the direct children of the root component have $store. (e) commit('m', a, b) gives the mutation both a and b.

Need a hint?

Think about sync/async and how many payload values exist.

Show the full solution

(a) False: mutations are synchronous; fetch belongs in actions. (b) True. (c) False: getters are reactive and cached. (d) False: all descendants. (e) False: only the first extra argument is the payload; send an object.

Your turn: 3. Predict the output
const store = new Vuex.Store({
  state: { n: 1 },
  mutations: { add(state, k) { state.n += k } },
  actions: { addTwice({ commit }, k) { commit('add', k); commit('add', k) } }
})
store.dispatch('addTwice', 3)
console.log(store.state.n)
Need a hint?

Is anything asynchronous here?

Show the full solution

7. The action is not async and has no await, so both commits run immediately and n becomes 1 + 3 + 3 = 7 before the next line.

Your turn: 4. Why does the user get logged out?

The login action calls commit('setToken', token) which sets state.token = token. Users report being logged out on refresh. Fix it.

Need a hint?

Where does state.token get its starting value?

Show the full solution

Memory is lost on refresh. Initialise from storage: token: localStorage.getItem('authToken') || null, and in the mutation also call localStorage.setItem('authToken', token). Remove it on logout.

Exam radar

Exam radar

Counted by keywords in the graded papers, about 48 distinct Vuex-related questions were found, with roughly 59 of 608 Quiz 2 questions and 82 of 1471 End Term questions matching the week's keywords (approximate, the keyword match also catches neighbours). Vuex is a favourite in Quiz 2 and the End Term. The real questions test:

  • the "fill code1 and code2" store question (awardees, students, total cost): context.commit in an action, one payload object, state. prefix inside the mutation (seen in 2022, 2023, 2024 papers);
  • which statements about Vuex are true: single source of truth, common solution for parent-child and sibling data, multiple views on the same state, actions can be async; false: "this.$vuexstore", "a component cannot have local state", "getters are not reactive";
  • mutation versus action: mutations directly modify state and are synchronous; only actions make API calls;
  • sequence: dispatch, commit, state change;
  • predict the output of an action with setTimeout/await and a console log, getter counts after a mutation, devtools history of commit vs dispatch vs direct push;
  • mapState with the spread syntax; and token persistence via sessionStorage after refresh.
Watch out - real exam trap
  • this.$store.commit inside an action, or context.commit inside a component: wrong place.
  • commit('x', a, b): only a arrives.
  • Writing awardees.push(...) or capital without state. / company. inside a mutation.
  • Declaring a mutation async or putting fetch in it.
  • mapState([...]) without the spread when you also have other computed properties.
  • Thinking the first console line after dispatch shows the final value.

Quick quiz

Quick quiz

In Vuex, what is the purpose of mutations?

  • To commit asynchronous operations
  • To directly (and synchronously) change the state
  • To get the state
  • To define computed properties
Mutations are the only place where state is changed, and they must be synchronous.

Which statements about Vuex are correct? (select all that apply)

  • Vuex is preferred when multiple views depend on the same state.
  • Vuex provides actions for asynchronous operations.
  • Vuex getters are not reactive.
  • Only direct children of the root can access the store.
Getters are reactive and cached, and all descendants get $store.

What is the correct sequence to update state after an async operation?

  • State, dispatch, commit, state change
  • Dispatch an action, commit a mutation, state change
  • Commit a mutation, dispatch an action, state change
  • State change, commit, dispatch
A component dispatches an action; the action commits a mutation; the mutation changes state.

Which snippet is a correct mutation?

  • ADD(state, p) { state.cart.push(p) }
  • ADD(state, p) { setTimeout(() => state.cart.push(p), 500) }
  • ADD(state, p) { fetch('/api').then(() => state.cart.push(p)) }
  • ADD(state, p) { Promise.resolve().then(() => state.cart.push(p)) }
Only the first changes state synchronously; the others change it later.

Inside a Vuex action, which line correctly updates the store with a min value and a token?

  • this.$store.commit('update_final', 40000, token)
  • context.commit('update_final', 40000, token)
  • context.commit('update_final', { min: 40000, token: token })
  • this.$store.update_final({ min: 40000 })
Actions use context, and a payload is one value, so wrap several values in an object.

After login the token is saved in state.token and also in sessionStorage, and state.token starts as sessionStorage.getItem('authToken') || null. The user refreshes. What happens?

  • Both are lost
  • Vuex keeps it, sessionStorage resets
  • The Vuex state loads the token from sessionStorage again
  • sessionStorage is cleared on refresh
sessionStorage survives a refresh (not closing the tab), and the store reads it at start.

Viva: say it out loud

Viva: say it out loud
Why did you use Vuex in your project?

Several components, such as the navbar, the dashboard and the router guards, need the same data: the logged-in user, role and token. Passing it through props would be prop drilling, and an event bus has no single record of changes. Vuex gives one reactive source of truth and every change is a named mutation I can inspect in devtools.

Difference between a mutation and an action?

A mutation is a synchronous function that changes state directly, and it is the only way to do so. An action can be asynchronous, can call the API with fetch, and changes state only by committing mutations. I call mutations with commit and actions with dispatch.

Why must mutations be synchronous?

Devtools records the state before and after each mutation to show history and allow time travel. If a mutation changed state later in a callback, the tool could not link the change to the mutation. So async work goes into actions.

What happens to the store when I refresh the page?

The store lives in memory, so it is rebuilt empty. I keep the token in localStorage, write it in the setToken mutation, and initialise state.token from localStorage at startup. That way the user stays logged in.

Where do you call your API and why?

In actions. An action can wait for the response with await, handle errors, and then commit a mutation with the data. The component only dispatches the action and reads the result through computed properties or mapState.

What are getters and what are modules?

A getter is a cached, reactive computed value derived from state, such as the count of active users. A module is a separate slice of the store with its own state, mutations and actions; with namespaced: true its names get a prefix like cart/add, which avoids clashes in big apps.

When would you not use Vuex?

For small apps, or for data that only one component needs, such as a form field or a dropdown flag. Props, events and local data() are simpler there. In Vue 3 the recommended store is Pinia.

Recap

Remember this
  • Vuex = one reactive store shared by all components; data flows one way: dispatch, commit, mutate, render.
  • State is read (via computed, mapState); getters are cached derived values; mutations change state synchronously; actions do async work and commit.
  • commit('name', payload) and dispatch('name', payload): one payload. Components use this.$store, actions use context.
  • dispatch returns a Promise; code before the first await in an action runs immediately.
  • Modules split the store; namespaced: true adds the module/ prefix. Strict mode is for development.
  • The token in the store is lost on refresh; restore it from localStorage or sessionStorage in the initial state.
  • Vue 2 cannot detect new properties or index assignment: use Vue.set or replace the object.
  • Newer Vue uses Pinia; do not use Vuex for tiny or purely local data.
Code and facts to keep
  • new Vuex.Store({ state, getters, mutations, actions, modules, strict })
  • mutation (state, payload); getter (state, getters); action (context, payload) with { commit, dispatch, state, getters }
  • component: this.$store.state.x, this.$store.getters.x, this.$store.commit('m', p), this.$store.dispatch('a', p)
  • helpers: ...mapState(['x']), ...mapGetters(['g']) in computed; ...mapMutations(['m']), ...mapActions(['a']) in methods
  • namespaced: commit('cart/add', x), getters['cart/count'], mapState('cart', ['items'])
  • persistence: sessionStorage.setItem(k, v), getItem(k), removeItem(k); same API on localStorage; strings only
  • Reactivity fix: Vue.set(obj, 'key', val), arr.splice(i, 1, val)

Practise with real exam questions

These come straight from past papers (with the real figures and the official answer key). Open 60 real questions on this week   All Quiz 2 papers