Search⌘ K
AI Features

Getting Started: The Basics of Vuex

Explore the fundamentals of Vuex in this lesson, where you'll learn how to manage application-wide state efficiently. Understand the structure of Vuex including state, getters, mutations, and actions, and discover how Vuex helps avoid prop drilling and simplifies data sharing across components in your Vue apps.

We'll cover the following...

Vuex was, at least until Vue 3 arrived, the standard state machine for Vue. It serves as an app-wide state machine that we can also use across multiple apps on the same page. An example would be if you implemented certain dynamic parts of a server-side rendered application as small Vue apps. These Vue apps can share their state with Vuex, such as user data.

The Flux pattern

Vuex is loosely based on React’s Redux which, in turn, is based on the Flux pattern, which Facebook introduced. Flux solves many problems of classic Model-View-Controller (MVC) in the frontend. In a traditional MVC structure, the View displays data it receives from the Model. For example, we usually use a Controller to update the Model when user input occurs. The Model then informs the View about a change.

The more Models, Views, and Controllers we add, the more complex the structure gets. For each added Model, there are potential actions a user can take and, therefore, added Controllers. In addition, each added View needs updates from a Model and can potentially call Controllers to update Models. It gets confusing relatively fast.

The Flux pattern solves this problem by introducing a state machine that keeps track of all the Models and poses a centralized point to call actions. Any View trigger updates to the store by speaking to an action creator which will, in turn, talk to a dispatcher, which will then propagate updates to the stores.

The problems Vuex solves

The restructuring of the data flow solves a few practical problems. One of them is prop drilling, a technique to pass data down several components via props. We can mitigate prop drilling by using provide and inject, but a state machine is a more general approach. The more we use provide and inject, the harder it gets to understand where data is coming from and where it is used.

We can also use Vuex to abstract data fetching away from the components. The store is usually available very early on, and we can use it to fetch and store data we need in many different components. Think of user data—the username and avatar are something that we could use in several places in an app. Instead of fetching the data for every usage, we fetch it once upfront with Vuex.

A Vuex store also helps us to test our application. The data store and, to some degree, the logic are decoupled from the presentation, making unit tests simpler to write. We can also use a specific Vuex store to stub API responses and make our components testable. The essential parts

A Vuex state machine consists of four parts:

  • State: This is the actual data being held.
  • Getters: They work much like computed props on components.
  • Mutations: We use mutations to alter one or more attributes of the state.
  • Actions: This is something that needs to be done in the store. For example, committing multiple mutations or fetching data from an API.

To install Vuex, we need to execute npm install vuex --save in our CLI.

Note: Be aware of the version. Vuex 3 still supports Vue 2, while Vuex 4 only supports Vue 3.

So, let’s define a simple Vuex state and use it in our app, as illustrated below:

Javascript (babel-node)
import { createApp } from 'vue'
import { createStore } from 'vuex'
import App from './App.vue'
const app = createApp(App)
const state = {
counter: 0,
}
const store = createStore({
state
})
app.use(store)
app.mount('#app')

We consider it good practice to move the store (including the initialization) to its own JS file that we can import and use. When we install and use Vuex, we also see a new tab in the Vue Devtools called “Vuex.” This lets us inspect and change the global state.

We can define getters in their own object key when initializing the Vuex store. Getters are functions that receive the current state and, optionally, all other getters from the store. We usually use getters to abstract away logic that we need to process the state. For example, think of a user object with a firstName and a lastName key. Instead of defining a computed prop called fullName on any component that needs it, we can use a getter for this, as shown below:

Javascript (babel-node)
const state = {
firstName: 'Foo',
lastName: 'Bar',
}
const getters = {
fullName(state) {
return state.firstName + ' ' + state.lastName // "Foo Bar"
}
}
export default {
state,
getters
}

Getters are reactive. Any change to data in the state will also trigger a change to the computed value in the getter. Any component using the getter will receive an update. Getters may also return functions we could use to select different fields from states:

Javascript (babel-node)
const state = {
firstName: 'Foo',
lastName: 'Bar',
}
const getters = {
fullName(state) {
return state.firstName + ' ' + state.lastName
},
selectedName(state) {
return selected => {
return selected === 'first' ? state.firstName : state.lastName
}
}
}
export default {
state,
getters
}

To access the state and any getters, we use mapState and mapGetters in our components respectively. These two functions return an array of functions we can use as computed properties. For example, let’s use the above Vuex store and access all properties in a component.

HTML
<template>
<div>
<p>
Hello, {{ firstName }}!
</p>
<p>
Is it true that your last name is {{ lastName }}?
</p>
<p>
Then your full name must be {{ fullName }}!
</p>
<p>
We shall call you {{ selectedName('first') }} for simplicity.
</p>
</div>
</template>
<script>
import { mapState, mapGetters } from 'vuex'
export default {
computed: {
...mapState(['firstName', 'lastName']),
...mapGetters(['fullName', 'selectedName']),
}
}
</script>

Let’s practice. The Vue app below has a state it defines in store/store.js. In the store, there are two numbers, numberA and numberB. We want to do math with them. The App.vue component has a dropdown with different operations. We want to display both numbers in App.vue as well as the chosen operation and the result, such as 12 + 13 = 25.

Transform the state into a Vuex store and use it in the app. Display all fields of the store in the app. Also, add getters. Then, define a getter for the calculation in a method-style getter. It should receive the operation as a string and return the result. The app will throw errors on the first run though because there are things in use that need to be implemented.

<template>
  <div>
    <select v-model="operation">
      <option value="+">+</option>
      <option value="-">-</option>
      <option value="*">*</option>
      <option value="/">/</option>
    </select>

    <p>
      {{ numberA }} {{ operation }} {{ numberB }} = {{ calculatedResult }}
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      numberA: 12,
      numberB: 13,
      operation: '+',
      calculatedResult: 12 + 13
    }
  },
}
</script>

A Vue app using Vuex which should do mathematics

The hint below contains a possible solution to the puzzle above:

We see that even without mutating the state, we can still achieve a lot of functionality by only using getters and the state. Vuex unfolds its full potential with mutations and actions and serves best in large apps with a lot of shared data. However, it adds cognitive overhead. So, for small apps, it might cause harm to the developer experience.