Adding a Vuex Store

Having a database system such as MongoDB to manage our data is great, as we can use it to remotely request data for our routes whenever required. However, occasionally, we need to share some data across pages or components, and we don't want to make additional and unnecessary HTTP requests for this kind of data. Ideally, we would like to have a central place in our local app where we can store this "omnipresent" and centralized data. Fortunately, we have a system called Vuex to store this kind of data for us, and that is what you will explore in this chapter. So, in this chapter, you will learn how to use Vuex for state management (centralized data management) in your apps. You will learn about the Vuex architecture, its core concepts, and the suggested directory structure for managing modular Vuex stores. Lastly, you will learn how to activate and use a Vuex store in Nuxt apps.

The topics we will cover in this chapter are as follows:

  • Understanding the Vuex architecture
  • Getting started with Vuex
  • Understanding Vuex core concepts
  • Structuring Vuex store modules
  • Handling forms in a Vuex store
  • Using a Vuex store in Nuxt

Understanding the Vuex architecture

Before learning how to use a Vuex store in Nuxt apps, we should understand how it works in standard Vue apps. But what is Vuex? Let's find out in the upcoming section.

What is Vuex?

In a nutshell, Vuex is a centralized data (also referred as state) management system with some rules (which we will look into later) to ensure that the state can only be mutated predictably from multiple (distant) components that need to access the common data. This idea of information centralization is common with tools such as Redux in React. They all share a similar state management pattern with Vuex. Let's take a look at what this pattern is in the next section.

State management pattern

To understand the state management pattern in Vuex, let's take a look at a simple Vue app that we are already familiar with:

<div id="app"></div>

new Vue({
// state
data () {
return { message: '' }
},

// view
template: `
<div>
<p>{{ message }}</p>
<button v-on:click="greet">Greet</button>
</div>
`,

// actions
methods: {
greet () {
this.message = 'Hello World'
}
}
}).$mount('#app')

This simple app has the following parts:

  • state, which holds the source of the app
  • view, which maps the state
  • actions, which can be used to mutate the state from the view

They work perfectly and are easy to manage in a small app like this, but this simplicity becomes unsustainable and problematic when we have two or more components sharing the same state, or when we want to mutate the state with actions from different views.

Passing props can be the solution that pops into your mind, but this is tedious for deeply nested components. That's where Vuex comes in, extracting the common state and managing it globally in a specific location, called a store, so that any component can access it from anywhere, regardless of how deep it is nested.

Thus, separation using state management with some enforced rules can maintain the independence of the views and the state. Using this, we can make our code more structured and maintainable. Let's take a look at the architecture of Vuex in the following diagram:

Reference Source: https://vuex.vuejs.org/

In a nutshell, Vuex consists of actions, mutations, and the state. The state is always mutated through mutations, while mutations are always committed through the actions in the Vuex lifecycle. The mutated state is then rendered to the components and, at the same time, the actions are (usually) dispatched from the components. Communication with the backend API usually occurs in the actions. Let's get started with Vuex in the next section and dive into its constitutions.

Getting started with Vuex

As we mentioned in the previous section, all Vuex activities happen in a store, which can be created simply in your project root. However, while it seems simple, a Vuex store is different from a plain JavaScript object because a Vuex store is reactive, just like the two-way binding on an <input> element with the v-model directive. So, any state data you access in Vue components is reactively updated when it is changed in the store. The data in the store's state must be explicitly committed through mutations, just like we explained in the diagram in the previous section.

For this exercise, we will use a single-file component skeleton to build some simple Vue apps with Vuex. We will put all our sample code in /chapter-10/vue/vuex-sfc/ in our GitHub repository. Let's get started.

Installing Vuex

Before we can create a Vuex store, we must install Vuex and import it using the following steps:

  1. Install Vuex by using npm:
$ npm i vuex
  1. Import and register it by using the Vue.use() method:
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

Remember that the preceding installation steps are meant to use Vuex with a module system, which is what we are going for in this chapter. But before jumping into a module system app, we should take a look at how we can create the Vuex app by using CDN or a direct download in the next section.

Note that Vuex requires Promise support. If your browser does not support Promise, please check out how you can install a polyfill library for your apps at https://vuex.vuejs.org/installation.html#promise.

Creating a simple store

We can start with a simple store by using CDN or direct download with the following steps:

  1. Install Vue and Vuex with the HTML <script> blocks:
<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>
  1. Activate the Vuex store in the HTML <body> block:
<script type="text/javascript">
const store = new Vuex.Store({
state: { count: 0 },
mutations: {
increment (state) { state.count++ }
}
})
store.commit('increment')
console.log(store.state.count) // -> 1
</script>

You can see from this code that you just need to create the Vuex state in a JavaScript object, a mutation method, and then you can access the state object with the store's state key and trigger the change in the state with the store's commit method, as follows:

store.commit('increment')
console.log(store.state.count)

In this simple example, we have complied with one of the enforced rules in Vuex, which is changing the state data by committing the mutation instead of changing it directly. Let's dive into the core concepts of Vuex and other rules by creating module system apps in the next section.

Understanding Vuex core concepts

There are five core concepts in Vuex that we will guide you through in this section. They are state, getters, mutations, actions, and modules. We will start by looking into the state concept first in the following section.

The state

The state is the heart of a Vuex store. It is the source of the "global" data that we can manage and maintain in a structured and predictable way in Vuex. A state in Vuex is a single state tree–a single JavaScript object that contains all the app state data. So, you will usually have one store per app. Let's take a look at how we can get the state into components in the following sections.

Accessing the state

As we mentioned in the previous section, Vuex stores are reactive, but if we want to access the reactive value in the view, we should use the computed property instead of the data method, as follows:

// vuex-sfc/state/basic/src/app.vue
<p>{{ number }}</p>

import Vue from 'vue/dist/vue.js'
import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Vuex.Store({
state: { number: 1 }
})

export default {
computed: {
number () {
return store.state.number
}
}
}

So now, the number field in the <template> block is reactive and the computed property will re-evaluate and update the DOM whenever store.state.number changes. But this pattern causes a coupling issue and is against the extracting idea of Vuex. So, let's refactor the preceding code with the following steps:

  1. Extract the store to the root component:
// vuex-sfc/state/inject/src/entry.js
import Vue from 'vue/dist/vue.js'
import App from './app.vue'

import Vuex from 'vuex'
Vue.use(Vuex)

const store = new Vuex.Store({
state: { number: 0 }
})

new Vue({
el: 'app',
template: '<App/>',
store,
components: {
App
}
})
  1. Remove the store from the child component but keep the computed property as it is:
// vuex-sfc/state/inject/src/app.vue
<p>{{ number }}</p>

export default {
computed: {
number () {
return this.$store.state.number
}
}
}

In the updated code, the store is now injected into the child component and you can access it by using this.$store from the component. However, this pattern can get repetitive and verbose when you have lots of store state properties that you need to compute with the computed property. In this case, we can use the mapState helper to lift the burden. Let's take a look at how we can use it in the next section.

The mapState helper

We can use the mapState helper to help us generate the computed state functions to save some lines and keystrokes with the following steps:

  1. Create a store with multiple state properties:
// vuex-sfc/state/mapstate/src/entry.js
const store = new Vuex.Store({
state: {
experience: 1,
name: 'John',
age: 20,
job: 'designer'
}
})
  1. Import the mapState helper from Vuex and pass the state properties as an array to the mapState method:
// vuex-sfc/state/mapstate/src/app.vue
import { mapState } from 'vuex'

export default {
computed: mapState([
'experience', 'name', 'age', 'job'
])
}

This works perfectly as long as the name of a mapped computed property is the same as a state property name. However, it is better to use it with the object spread operator so that we can mix multiple mapState helpers in the computed property:

computed: {
...mapState({
// ...
})
}

For example, you may want to compute the state data with the data in the child component, as follows:

// vuex-sfc/state/mapstate/src/app.vue
import { mapState } from 'vuex'

export default {
data () {
return { localExperience: 2 }
},
computed: {
...mapState([
'experience', 'name', 'age', 'job'
]),
...mapState({
experienceTotal (state) {
return state.experience + this.localExperience
}
})
}
}

You also can pass a string value to make an alias for the experience state property, as follows:

...mapState({
experienceAlias: 'experience'
})
  1. Add the computed state properties to <template>, as follows:
// vuex-sfc/state/mapstate/src/app.vue
<p>{{ name }}, {{ age }}, {{ job }}</p>
<p>{{ experience }}, {{ experienceAlias }}, {{ experienceTotal }}</p>

You should get the following result on your browser:

John, 20, designer
1, 1, 3

You may be wondering that since we can compute the state data in the child component, can we compute the state data in the store itself? The answer is yes, we can do so with getters, which we will cover in the next section. Let's get to it.

Getters

You can define getter methods in the getters property in the store to compute the state before it is used in the view by the child components. Just like the computed property, the computed result in a getter is reactive, but it is cached and will be updated whenever its dependencies are changed. A getter takes the state as its first argument and getters as the second argument. Let's create some getters and use them in the child component with the following steps:

  1. Create a store with a state property with a list of items and some getters for accessing these items:
// vuex-sfc/getters/basic/src/entry.js
const store = new Vuex.Store({
state: {
fruits: [
{ name: 'strawberries', type: 'berries' },
{ name: 'orange', type: 'citrus' },
{ name: 'lime', type: 'citrus' }
]
},
getters: {
getCitrus: state => {
return state.fruits.filter(fruit => fruit.type === 'citrus')
},
countCitrus: (state, getters) => {
return getters.getCitrus.length
},
getFruitByName: (state, getters) => (name) => {
return state.fruits.find(fruit => fruit.name === name)
}
}
})

In this store, we created the getCitrus method to get all the items with a type of citrus and the countCitrus method to depend on the result in the getCitrus method. The third method, getFruitByName, is used to get a specific item in the list by citrus name.

  1. Create some methods in the computed property to execute the getters in the store, as follows:
// vuex-sfc/getters/basic/src/app.vue
export default {
computed: {
totalCitrus () {
return this.$store.getters.countCitrus
},
getOrange () {
return this.$store.getters.getFruitByName('orange')
}
}
}
  1. Add the computed state properties to <template>, as follows:
// vuex-sfc/getters/basic/src/app.vue
<p>{{ totalCitrus }}</p>
<p>{{ getOrange }}</p>

You should get the following result in your browser:

2
{ "name": "orange", "type": "citrus" }

The same as the mapState helper, we can use the mapGetters helper in the computed properties, which saves us some lines and keystrokes. Let's get to it in the next section.

The mapGetters helper

Just like the mapState helper, we can use the mapGetters helper to map the store getters in the computed properties. Let's see how we can use it with the following steps:

  1. Import the mapGetters helper from Vuex and pass the getters as an array to the mapGetters method with the object spread operator so that we can mix multiple mapGetters helpers in the computed property:
// vuex-sfc/getters/mapgetters/src/app.vue
import { mapGetters } from 'vuex'

export default {
computed: {
...mapGetters([
'countCitrus'
]),
...mapGetters({
totalCitrus: 'countCitrus'
})
}
}

In the preceding code, we created an alias for the countCitrus getter by passing the string value to the totalCitrus key. Note that with the object spread operator, we also can mix other vanilla methods in the computed property. So, let's add a vanilla getOrange getter method to the computed option on top of these mapGetters helpers, as follows:

// vuex-sfc/getters/mapgetters/src/app.vue
export default {
computed: {
// ... mapGetters
getOrange () {
return this.$store.getters.getFruitByName('orange')
}
}
}
  1. Add the computed state properties to <template>, as follows:
// vuex-sfc/getters/mapgetters/src/app.vue
<p>{{ countCitrus }}</p>
<p>{{ totalCitrus }}</p>
<p>{{ getOrange }}</p>

You should get the following result in your browser:

2
2
{ "name": "orange", "type": "citrus" }

So far, you have learned how to access the state in the store by using the computed methods and getters. What about changing the state? Let's get to it in the next section.

Mutations

Just as we mentioned in the previous sections, the store state must be explicitly committed through mutations. A mutation is simply a function just like any other function you have learned about in the store properties, but it must be defined in the mutations property in the store. It always takes the state as the first argument. Let's create some mutations and use them in the child component with the following steps:

  1. Create a store with a state property and some mutation methods that we can use to mutate the state, as follows:
// vuex-sfc/mutations/basic/src/entry.js
const store = new Vuex.Store({
state: { number: 1 },
mutations: {
multiply (state) {
state.number = state.number * 2
},
divide (state) {
state.number = state.number / 2
},
multiplyBy (state, n) {
state.number = state.number n
}
}
})
  1. Create the following methods in the component to add a call to commit the mutation by using this.$store.commit:
// vuex-sfc/mutations/basic/src/app.js
export default {
methods: {
multiply () {
this.$store.commit('multiply')
},
multiplyBy (number) {
this.$store.commit('multiply', number)
},
divide () {
this.$store.commit('divide')
}
}
}

Like getter methods, you also can use the mapMutations helper on mutation methods, so let's get to it in the next section.

The mapMutations helper

We can use the mapMutations helper to map the component methods to the mutation methods with object spread operators so that we can mix multiple mapMutations helpers in the method property. Let's see how we can do it with the following steps:

  1. Import the mapMutations helper from Vuex and pass the mutations as an array to the mapMutations method with object spread operators, as follows:
// vuex-sfc/mutations/mapmutations/src/app.vue
import { mapMutations } from 'vuex'

export default {
computed: {
number () {
return this.$store.state.number
}
},
methods: {
...mapMutations([
'multiply',
'multiplyBy',
'divide'
]),
...mapMutations({
square: 'multiply'
})
}
}
  1. Add the computed state property and the methods to <template>, as follows:
// vuex-sfc/mutations/mapmutations/src/app.vue
<p>{{ number }}</p>
<p>
<button v-on:click="multiply">x 2</button>
<button v-on:click="divide">/ 2</button>
<button v-on:click="square">x 2 (square)</button>
<button v-on:click="multiplyBy(10)">x 10</button>
</p>

You should see that the number state is reactively being multiplied or divided on your browser when you click on the preceding buttons. In this example, we have managed to change the state value through mutations, which is one of the rules in Vuex. Another rule is that we must not make asynchronous calls in mutations. In other words, mutations must be synchronous so that every mutation can be logged by the DevTool for debugging. If you want to make asynchronous calls, use actions, which we will walk you through in the next section. Let's get to it.

Actions

Actions are functions just like mutations, except they are not used for mutating the state but committing the mutations. Unlike mutations, actions can be asynchronous. We create action methods in the actions property in the store. An action method takes the context object as its first argument, your custom parameters as the second argument, and so forth. You can use context.commit to commit a mutation, context.state to access the state, and context.getters to access the getters. Let's add some action methods with the following steps:

  1. Create a store with a state property and the action methods, as follows:
// vuex-sfc/actions/basic/src/entry.js
const store = new Vuex.Store({
state: { number: 1 },
mutations: { ... },
actions: {
multiplyAsync (context) {
setTimeout(() => {
context.commit('multiply')
}, 1000)
},
multiply (context) {
context.commit('multiply')
},
multiplyBy (context, n) {
context.commit('multiplyBy', n)
},
divide (context) {
context.commit('divide')
}
}
})

In this example, we used the same mutations from the previous section and created the action methods, one of them making an asynchronous action method to demonstrate why we need actions for asynchronous calls, even though they seem tedious at first.

Note that if you prefer, you can destructure context with an ES6 JavaScript destructuring assignment and import the commit property directly, as follows:

divide ({ commit }) {
commit('divide')
}
  1. Create a component and dispatch the preceding actions with this.$store.commit, as follows:
// vuex-sfc/actions/basic/src/app.js
export default {
methods: {
multiply () {
this.$store.dispatch('multiply')
},
multiplyAsync () {
this.$store.dispatch('multiplyAsync')
},
multiplyBy (number) {
this.$store.dispatch('multiply', number)
},
divide () {
this.$store.dispatch('divide')
}
}
}

Like mutation and getter methods, you also can use the mapActions helper on action methods, so let's get to it in the next section.

The mapActions helper

We can use the mapActions helper to map the component methods to the action methods with object spread operators so that we can mix multiple mapActions helpers in the method property. Let's see how we can do so with the following steps:

  1. Import the mapActions helper from Vuex and pass the mutations as an array to the mapActions method with object spread operators, as follows:
// vuex-sfc/actions/mapactions/src/app.vue
import { mapActions } from 'vuex'

export default {
methods: {
...mapActions([
'multiply',
'multiplyAsync',
'multiplyBy',
'divide'
]),
...mapActions({
square: 'multiply'
})
}
}
  1. Add the computed state property and bind the methods to <template>, as follows:
// vuex-sfc/mapactions/src/app.vue
<p>{{ number }}</p>
<p>
<button v-on:click="multiply">x 2</button>
<button v-on:click="square">x 2 (square)</button>
<button v-on:click="multiplyAsync">x 2 (multiplyAsync)</button>
<button v-on:click="divide">/ 2</button>
<button v-on:click="multiplyBy(10)">x 10</button>
</p>

export default {
computed: {
number () {
return this.$store.state.number
}
},
}

You should see that the number state is reactively being multiplied or divided on your browser when you click on the preceding buttons. In this example, again, we have managed to change the state value by committing mutations through actions that can only be dispatched by using the store dispatch method. These are the enforced rules that we must comply with when applying a store to our app.

However, when the store and the app grow, we might want to separate the state, mutations, and actions into groups. In this case, we will need the last concept in Vuex–modules–which is covered in the next section we will walk you through. Let's get to it.

Modules

We can divide our store into modules to scale the app. Each module can have a state, mutations, actions, and getters, as follows:

const module1 = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}

const module2 = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}

const store = new Vuex.Store({
modules: {
a: module1,
b: module2
}
})

You can then access each module's state or other properties, as follows:

store.state.a
store.state.b

When writing modules for your store, you should understand the local state, the root state, and the namespacing in the store's modules. Let's look at them in the following sections.

Understanding the local state and root state

The mutations and getters in each module will receive the module's local state as their first argument, as follows:

const module1 = {
state: { number: 1 },
mutations: {
multiply (state) {
console.log(state.number)
}
},

getters: {
getNumber (state) {
console.log(state.number)
}
}
}

In this code, the state in the mutation and getter methods is the local module state, so you will get 1 for console.log(state.number), while in each module's actions, you will get the context as the first argument, which you can use to access the local state and root state as context.state and context.rootState, as follows:

const module1 = {
actions: {
doSum ({ state, commit, rootState }) {
//...
}
}
}

The root state is also available in each module's getters as the third argument, as follows:

const module1 = {
getters: {
getSum (state, getters, rootState) {
//...
}
}
}

The local state from the modules and the root state from the store root can get mixed up and become confusing when we have multiple modules. This brings us to namespacing, which can make our modules more self-contained and less likely to conflict with each other. Let's get to it in the next section.

Understanding namespacing

By default, the actions, mutations, and getters properties in each module are registered under the global namespace, so the key or method name in each of these properties must be unique. In other words, a method name cannot be repeated in two different modules, as follows:

// entry.js
const module1 = {
getters: {
getNumber (state) {
return state.number
}
}
}

const module2 = {
getters: {
getNumber (state) {
return state.number
}
}
}

For the preceding example, you will see the following error due to using the same method name in the getters:

[vuex] duplicate getter key: getNumber

So, to avoid duplication, the method name must be explicitly named for each module, as follows:

getNumberModule1
getNumberModule2

Then, you can access these methods in the child component and map them, as follows:

// app.js
import { mapGetters } from 'vuex'

export default {
computed: {
...mapGetters({
getNumberModule1: 'getNumberModule1',
getNumberModule2: 'getNumberModule2'
})
}
}

These methods also can be written as follows if you don't want to use mapGetters as in the preceding code:

// app.js
export default {
computed: {
getNumberModule1 (state) {
return this.$store.getters.getNumberModule1
},
getNumberModule2 (state) {
return this.$store.getters.getNumberModule2
}
}
}

However, this pattern may look verbose as we have to write this.$store.getters or this.$store.actions repetitively for every method we create in the store. It is the same for accessing the state of each module, as follows:

// app.js
export default {
computed: {
...mapState({
numberModule1 (state) {
return this.$store.state.a.number
}
}),
...mapState({
numberModule2 (state) {
return this.$store.state.b.number
}
})
}
}

So, the solution for a situation like this is to use namespacing for each module by setting the namespaced key in each module to true, as follows:

const module1 = {
namespaced: true
}

When the module is registered, all of its getters, actions, and mutations will be automatically namespaced based on the path the module is registered at. Take the following example:

// entry.js
const module1 = {
namespaced: true
state: { number:1 }
}

const module2 = {
namespaced: true
state: { number:2 }
}

const store = new Vuex.Store({
modules: {
a: module1,
b: module2
}
})

Now, you can access the state of each module with less code that is more easily readable, as follows:

// app.js
import { mapState } from 'vuex'


export default {
computed: {
...mapState('a', {
numberModule1 (state) {
return state.number
}
}),
...mapState('b', {
numberModule2 (state) {
return state.number
}
})
}
}

For the preceding example code, you will get 1 for numberModule1 and 2 for numberModule2. Besides, you can also eliminate the error of "duplicate getter keys" by using namespacing. So now, you can have more "abstract" names for methods, as follows:

// entry.js
const module1 = {
getters: {
getNumber (state) {
return state.number
}
}
}

const module2 = {
getters: {
getNumber (state) {
return state.number
}
}
}

Now, you can call and map these methods precisely with the namespaces that they are registered under, as follows:

// app.js
import { mapGetters } from 'vuex'

export default {
computed: {
...mapGetters('a', {
getNumberModule1: 'getNumber',
}),
...mapGetters('b', {
getNumberModule2: 'getNumber',
})
}
}

We have been writing the store in the root file, entry.js. Whether you are writing to a modular store or not, this root file will get bloated when the state properties and the methods in mutations, getters, and actions grow over time. So, this brings us to the next section, where you will learn how to separate and structure these methods and state properties in single files of their own. Let's get to it.

Structuring Vuex store modules

In a Vue app, as long as you comply with the enforced rules we went through in the previous sections, there are no strict restrictions on how you should structure your store. Depending on how complex your store is, there are two recommended structures in this book that you can use in the following sections. Let's get started.

Creating a simple store module structure

In this simple module structure, you can have a /store/ directory that contains a /modules/ directory that keeps all the modules in this folder. Here are the steps to create this simple project structure:

  1. Create a /store/ directory with a /modules/ directory in it with the store modules, as follows:
// vuex-sfc/structuring-modules/basic/
├── index.html
├── entry.js
├── components
│ ├── app.vue
│ └── ...
└── store
├── index.js
├── actions.js
├── getters.js
├── mutations.js
└── modules
├── module1.js
└── module2.js

In this simple structure, /store/index.js is where we assemble modules from the /modules/ directory and export the store, along with the root's state, actions, getters, and mutations, as follows:

// store/index.js
import Vue from 'vue'
import actions from './actions'
import getters from './getters'
import mutations from './mutations'
import module1 from './modules/module1'
import module2 from './modules/module2'

import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
state: {
number: 3
},
actions,
getters,
mutations,
modules: {
a: module1,
b: module2
}
})
  1. Split the root's actions, mutations, and getters into separate files and assemble them in the root index file, as follows:
// store/mutations.js
export default {
mutation1 (state) {
//...
},
mutation2 (state, n) {
//...
}
}
  1. Create modules in .js files with their states, actions, mutations, and getters just like you have learned in the previous sections, as follows:
// store/modules/module1.js
export default {
namespaced: true,
state: {
number: 1
},
mutations: { ... },
getters: { ... },
actions: { ... }
}

If a module file gets too big, we can split the module's state, actions, mutations, and getters into separate files. This brings us to an advanced store module structure, which we will look at in the next section. Let's have a look.

Creating an advanced store module structure

In this advanced module structure, you can have a /store/ directory that contains a /modules/ directory that keeps all modules in subfolders of this folder. We can split a module's state, actions, mutations, and getters into separate files and then keep them in the module folder with the following steps:

  1. Create a /store/ directory that contains a /modules/ directory for the store modules, as follows:
// vuex-sfc/structuring-modules/advanced/
├── index.html
├── entry.js
├── components
│ └── app.vue
└── store
├── index.js
├── action.js
└── ...
├── module1
│ ├── index.js
│ ├── state.js
│ ├── mutations.js
│ └── ...
└── module2
├── index.js
├── state.js
├── mutations.js
└── ...

In this more complex project structure, /store/module1/index.js is where we assemble module1, while /store/module2/index.js is where we assemble module2, as follows:

// store/module1/index.js
import state from './state'
import getters from './getters'
import actions from './actions'
import mutations from './mutations'

export default {
namespaced: true,
state,
getters,
actions,
mutations
}

We also can split a module state into a single file, as follows:

// store/module1/state.js
export default () => ({
number: 1
})
  1. Split the module's actions, mutations, and getters into separate files to assemble them in the preceding module index file, as follows:
// store/module1/mutations.js
export default {
mutation1 (state) {
//...
},
mutation2 (state, n) {
//...
}
}
  1. Import the module index files to the store root where we assemble modules and export the store, as follows:
// store/index.js
import module1 from './module1'
import module2 from './module2'
  1. Switch on strict mode to ensure that the store state is only mutated in the mutations property, as follows:
const store = new Vuex.Store({
strict: true,
...
})

Using strict mode is good practice to remind us to mutate any state inside the mutations property. So, an error will be thrown during development whenever the store state is mutated outside of the mutations property. However, we should disable it for production because it can impair performance when there are a large number of state mutations in the store. So, we can dynamically turn it off with the build tools, as follows:

// store/index.js
const debug = process.env.NODE_ENV !== 'production'

const store = new Vuex.Store({
strict: debug,
...
})

However, there is a caveat to using strict mode for handling forms in the store, which we will cover in the next section.

Handling forms in a Vuex store

When we use two-way data binding with a v-model in a Vue app, the data in the Vue instance is synchronized with the v-model input field. So, when you type anything into the input field, the data will be updated immediately. However, this will create a problem in a Vuex store because we must not mutate the store state (data) outside the mutations property. Let's take a look at a simple two-way data binding in a Vuex store:

// vuex-non-sfc/handling-forms/v-model.html
<input v-model="user.message" />

const store = new Vuex.Store({
strict: true,
state: {
message: ''
}
})

new Vue({
el: 'demo',
store: store,
computed: {
user () {
return this.$store.state.user
}
}
})

For this example, you will see the following error message in your browser's debug tool when typing a message in the input field:

Error: [vuex] do not mutate vuex store state outside mutation handlers.

This is because the v-model attempts to mutate message in the store state directly when you type, so it results in an error in the strict mode. Let's see what options we have to resolve this in the following sections.

Using v-bind and v-on directives

In most cases, two-way binding is not always suitable. It makes more sense to use one-way binding and explicit data updates in Vuex by binding <input> with the value attribute on the input or change events. You can get this working easily with the following steps:

  1. Create a method for mutating the state in the mutations property just as you learned in the previous sections:
// vuex-sfc/form-handling/value-event/store/index.js
export default new Vuex.Store({
strict: true,
state: {
message: ''
},
mutations: {
updateMessage (state, message) {
state.message = message
}
}
})
  1. Bind the <input> element with the value attribute and the input event with the methods, as follows:
// vuex-sfc/form-handling/value-event/components/app.vue
<input v-bind:value="message" v-on:input="updateMessage" />

import { mapState } from 'vuex'

export default {
computed: {
...mapState({
message: state => state.message
})
},
methods: {
updateMessage (e) {
this.$store.commit('updateMessage', e.target.value)
}
}
}

In this solution, we use the updateMessage method in the child component to commit the mutation method, updateMessage, in the store and pass the value from the input event. By only committing the mutation explicitly like this, we are not going against the enforced rule in Vuex that we must comply with. So, adopting this solution means you cannot use the v-model to handle forms for the Vuex store. However, you can still use it if you are using the computed getter and setter from Vue itself. Let's look at this in the next section.

Using a two-way computed property

We can use Vue's built-in, two-way computed property with a setter for handling forms with a v-model with the help of the following steps:

  1. Create a method for mutating the state in the mutations property, just as in the previous section.
  2. Apply the get and set methods to the message key, as follows:
// vuex-sfc/form-handling/getter-setter/components/app.vue
<input v-model="message" />

export default {
computed: {
message: {
get () {
return this.$store.state.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
}

However, this probably works well for simple computed properties. If you have a deep level object with more than 10 keys to update, you will need 10 sets of two-way computed properties (the getter and setter). The code will eventually get more repetitive and verbose than the event-based solution.

Well done! You have managed to get through the foundation and concept of a Vuex store. You have learned how to use a store in a Vue app. Now, it is time to move on to applying a store in Nuxt. So, let's get to it in the next section.

If you want to find out more information about Vuex, please visit https://vuex.vuejs.org/.

Using a Vuex store in Nuxt

In Nuxt, Vuex is already installed for you. You only need to be sure that the /store/ directory is present in the project root. If you are installing your Nuxt project using create-nuxt-app, this directory is autogenerated for you during project installation. In Nuxt, you can create your store in two different modes:

  • Module
  • Classic (deprecated)

Since the classic mode is deprecated, we will only focus on the module mode in this book. So, let's get started in the following section.

You can find the source code for all of the following Nuxt examples in /chapter-10/nuxt-universal/ in our GitHub repository. 

Using module mode

Unlike Vue apps, in Nuxt, the namespaced key is set to true by default for each module, as well as the root module. Also, in Nuxt, you do not need to assemble modules in the store root; you just need to export the state as a function, and the mutations, getters, and actions as objects in the root and module files. Let's get things going with the following steps:

  1. Create a store root, as follows:
// store/index.js
export const state = () => ({
number: 3
})

export const mutations = {
mutation1 (state) { ... }
}

export const getters = {
getter1 (state, getter) { ... }
}

export const actions = {
action1 ({ state, commit }) { ... }
}

In Nuxt, Vuex's strict mode is set to true by default during development and turned off automatically in production mode, but you can disable that during development, as follows:

// store/index.js
export const strict = false
  1. Create a module, as follows:
// store/module1.js
export const state = () => ({
number: 1
})

export const mutations = {
mutation1 (state) { ... }
}

export const getters = {
getter1 (state, getter, rootState) { ... }
}

export const actions = {
action1 ({ state, commit, rootState }) { ... }
}

Then, just as we did it manually in a Vue app in the previous section, the store will be autogenerated, as follows:

new Vuex.Store({
state: () => ({
number: 3
}),
mutations: {
mutation1 (state) { ... }
},
getters: {
getter1 (state, getter) { ... }
},
actions: {
action1 ({ state, commit }) { ... }
},
modules: {
module1: {
namespaced: true,
state: () => ({
number: 1
}),
mutations: {
mutation1 (state) { ... }
}
...
}
}
})
  1. Map all the store states, getters, mutations, and actions in the <script> block of any page, as follows:
// pages/index.vue
import { mapState, mapGetters, mapActions } from 'vuex'

export default {
computed: {
...mapState({
numberRoot: state => state.number,
}),
...mapState('module1', {
numberModule1: state => state.number,
}),
...mapGetters({
getNumberRoot: 'getter1'
}),
...mapGetters('module1', {
getNumberModule1: 'getter1'
})
},
methods: {
...mapActions({
doNumberRoot:'action1'
}),
...mapActions('module1', {
doNumberModule1:'action1'
})
}
}
  1. Display the computed properties with methods to commit mutations in the <template> block, as follows:
// pages/index.vue
<p>{{ numberRoot }}, {{ getNumberRoot }}</p>
<button v-on:click="doNumberRoot">x 2 (root)</button>

<p>{{ numberModule1 }}, {{ getNumberModule1 }}</p>
<button v-on:click="doNumberModule1">x 2 (module1)</button>

You should see the following initial results on your screen, and they will be mutated when you click on the preceding buttons that are displayed on the screen from the template:

3, 3
1, 1

As we mentioned earlier, you do not need to assemble modules in the store root in Nuxt because they are "assembled" for you by Nuxt, only if you use the following structure:

// chapter-10/nuxt-universal/module-mode/
└── store
├── index.js
├── module1.js
├── module2.js
└── ...

But if you were to use the structure that we used to assemble modules manually in the store root as we did for a Vue app, as follows:

// chapter-10/vuex-sfc/structuring-modules/basic/
└── store
├── index.js
├── ...
└── modules
├── module1.js
└── module2.js

You will get the following errors in the Nuxt app:

ERROR [vuex] module namespace not found in mapState(): module1/
ERROR [vuex] module namespace not found in mapGetters(): module1/

To fix these errors, you need to tell Nuxt explicitly where these modules are kept:

export default {
computed: {
..mapState('modules/module1', {
numberModule1: state => state.number,
}),
...mapGetters('modules/module1', {
getNumberModule1: 'getter1'
})
},
methods: {
...mapActions('modules/module1', {
doNumberModule1:'action1'
})
}
}

Just as with Vuex in Vue apps, we can split the state, actions, mutations, and getters into separate files in a Nuxt app, too. Let's see how we can do that and what the differences with Nuxt are in the next section.

Using module files

We can split big files in our modules into separate files – state.js, actions.js, mutations.js, and getters.js – for the store root and each module. So, let's do so with the following steps:

  1. Create separate files of the state, actions, mutations, and getters for the store root, as follows:
// store/state.js
export default () => ({
number: 3
})

// store/mutations.js
export default {
mutation1 (state) { ... }
}
  1. Create separate files of the state, actions, mutations, and getters for the module, as follows:
// store/module1/state.js
export default () => ({
number: 1
})

// store/module1/mutations.js
export default {
mutation1 (state) { ... }
}

Again, in Nuxt, we do not need to assemble these separate files with index.js as we do in a Vue app. Nuxt will do this for us as long as we use the following structure:

// chapter-10/nuxt-universal/module-files/
└── store
├── state.js
├── action.js
└── ...
├── module1
│ ├── state.js
│ ├── mutations.js
│ └── ...
└── module2
├── state.js
├── mutations.js
└── ...

We can compare this to the following structure that we used for the Vue app, where we needed an index.js file for the store root and each module to assemble the state, actions, mutations, and getters from the separate files:

// chapter-10/vuex-sfc/structuring-modules/advanced/
└── store
├── index.js
├── action.js
└── ...
├── module1
│ ├── index.js
│ ├── state.js
│ ├── mutations.js
│ └── ...
└── module2
├── index.js
├── state.js
├── mutations.js
└── ...

So, the store comes out of the box in Nuxt and it saves you some lines of code for assembling files and registering modules. Sweet, isn't it? Now, let's venture a bit further to see how we can fill the store state dynamically in Nuxt with the fetch method in the next section.

Using the fetch method

We can use the fetch method to fill the store state before a page is rendered. It works the same as the asyncData method, which we have already covered – it is called each time before a component is loaded. It is called on the server side once and then on the client side when navigating to other routes. Just like asyncData, we can use async/await with the fetch method for asynchronous data. It is called after the component is created, so we can access the component instance through this in the fetch method. So, we can access the store through this.$nuxt.context.store. Let's create a simple Nuxt app using this method with the following steps:

  1. Request the list of users from a remote API asynchronously with the fetch method in any page, as follows:
// pages/index.vue
import axios from 'axios'

export default {
async fetch () {
const { store } = this.$nuxt.context
await store.dispatch('users/getUsers')
}
}
  1. Create a user module with the state, mutations, and actions, as follows:
// store/users/state.js
export default () => ({
list: {}
})

// store/users/mutations.js
export default {
setUsers (state, data) {
state.list = data
},
removeUser (state, id) {
let found = state.list.find(todo => todo.id === id)
state.list.splice(state.list.indexOf(found), 1)
}
}

// store/users/actions.js
export default {
setUsers ({ commit }, data) {
commit('setUsers', data)
},
removeUser ({ commit }, id) {
commit('removeUser', id)
}
}

The setUsers methods in the mutations and actions are used to set the list of users to the state, while the removeUser methods are used to remove users from the state one at a time.

  1. Map the methods from the state and actions to the page, as follows:
// pages/index.vue
import { mapState, mapActions } from 'vuex'

export default {
computed: {
...mapState ('users', {
users (state) {
return state.list
}
})
},
methods: {
...mapActions('users', {
removeUser: 'removeUser'
})
}
}
  1. Loop and display the list of users in the <template> block, as follows:
// pages/index.vue
<li v-for="(user, index) in users" v-bind:key="user.id">
{{ user.name }}
<button class="button" v-on:click="removeUser(user.id)">Remove</button>
</li>

You should see the list of users on the screen when you load the app in a browser, and you can click to remove a user with the Remove button. We can also use async/await in the actions to fetch the remote data, as follows:

// store/users/actions.js
import axios from 'axios'

export const actions = {
async getUsers ({ commit }) {
const { data } = await axios.get('https://jsonplaceholder.typicode.com/users')
commit('setUsers', data)
}
}

Then, we can just dispatch the getUsers action, as follows:

// pages/index.vue
export default {
async fetch () {
const { store } = this.$nuxt.context
await store.dispatch('users/getUsers')
}
}

Besides fetching and populating the state with the fetch method in Nuxt, we also can use the nuxtServerInit action, which is only available in Nuxt. Let's move on to look at it in the next section.

Using the nuxtServerInit action

Unlike the asyncData method, which is only available in page-level components, and the fetch method, which is available in all Vue components (including page-level components), the nuxtServerInit action is a reserved store action method available only in the Nuxt store if it is defined. It only can be defined in the index.js file in the store root and will be called on the server side only, before the Nuxt app is initiated. Unlike the asyncData and fetch methods, which are called on the server side and then the client side on the subsequent routes, the nuxtServerInit action method is only called on the server side once, unless you refresh any page of the app in the browser. Also, unlike the asyncData method, which gets the Nuxt context object as its first argument, the nuxtServerInit action method gets it as its second argument. The first argument it receives is the store context object. Let's put these context objects into the following table: 

First argument Second argument
  • dispatch
  • commit
  • getters
  • state
  • rootGetters
  • rootState
  • isStatic
  • isDev
  • isHMR
  • app
  • req
  • res
  • ...

So, the nuxtServerInit action method is very useful when we want to get the data from the server side from any page in our app and then populate the store state with the server data–for example, the authenticated user data that we store in the session on the server side when a user has logged into our app. This session data can be stored as req.session.authUser in Express or ctx.session.authUser in Koa. Then, we can pass ctx.session to nuxtServerInit through the req object.

Let's create a simple user login app with this method action and use Koa as the server-side API, which you learned about in Chapter 8, Adding a Server-Side Framework. We just need a bit of modification on the server side before we can inject any data into the session and create a store with the nuxtServerIni action method, which we can do with the following steps:

  1. Install the session package, koa-session, using npm:
$ npm install koa-session
  1. Import and register the session package as a middleware, as follows:
// server/middlewares.js
import session from 'koa-session'

app.keys = ['some secret hurr']
app.use(session(app))
  1. Create two routes on the server side, as follows:
// server/routes.js
router.post('/login', async (ctx, next) => {
let request = ctx.request.body || {}
if (request.username === 'demo' && request.password === 'demo') {
ctx.session.authUser = { username: 'demo' }
ctx.body = { username: 'demo' }
} else {
ctx.throw(401)
}
})

router.post('/logout', async (ctx, next) => {
delete ctx.session.authUser
ctx.body = { ok: true }
})

In the preceding code, we use the /login route to inject the authenticated user data, authUser, into the Koa context, ctx, with the session, while /logout is used to unset the authenticated data.

  1. Create the store state with an authUser key to hold the authenticated data:
// store/state.js
export default () => ({
authUser: null
})
  1. Create a mutation method to set the data to the authUser key in the preceding state:
// store/mutations.js
export default {
setUser (state, data) {
state.authUser = data
}
}
  1. Create an index.js file in the store root with the following actions:
// store/index.js
export const actions = {
nuxtServerInit({ commit }, { req }) {
if (req.ctx.session && req.ctx.session.authUser) {
commit('setUser', req.ctx.session.authUser)
}
},
async login({ commit }, { username, password }) {
const { data } = await axios.post('/api/login', { username,
password })
commit('setUser', data.data)
},
async logout({ commit }) {
await axios.post('/api/logout')
commit('setUser', null)
}
}

In the preceding code, the nuxtServerInit action method is used to access the session data from the server and populate the store state by committing the setUser mutation method. The login and logout action methods are used to authenticate the user login credentials and unset them. Note that the session data is stored inside req.ctx as this book is using Koa as the server API. If you are using Express, use the following code:

actions: {
nuxtServerInit ({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
}
}

Just like the asyncData and fetch methods, the nuxtServerInit action method can be asynchronous, too. You just have to return a Promise, or use an async/await statement for the Nuxt server to wait for the action to be completed asynchronously, as follows:

actions: {
async nuxtServerInit({ commit }) {
await commit('setUser', req.ctx.session.authUser)
}
}

  1. Create a form to use the store's action methods, as follows:
// pages/index.vue
<form v-on:submit.prevent="login">
<input v-model="username" type="text" name="username" />
<input v-model="password" type="password" name="password" />
<button class="button" type="submit">Login</button>
</form>

export default {
data() {
return {
username: '',
password: ''
}
},
methods: {
async login() {
await this.$store.dispatch('login', {
username: this.username,
password: this.password
})
},
async logout() {
await this.$store.dispatch('logout')
}
}
}
We have simplified the preceding code and the code in step 6 to fit this page, but you can find the complete versions for them from our GitHub repository in /chapter-10/nuxt-universal/nuxtServerInit/.

Well done! You have finally made it through one of the exciting features of Nuxt and Vue–Vuex stores. This was a long chapter, but it is a very important one as we will need to come back to Vuex and use it often in the upcoming chapters. Now, let's summarize what you have learned in this chapter.

Summary

We have come a long way. In this chapter, you learned about the architecture, core concepts, module structures, and form handling in Vuex stores. At this point, you should know that a Vuex store simply relates to state (or data) centralization and management with some enforced rules that you must comply with. So, for any state property that you may have in your store, the correct way of accessing it is by computing it in the computed property in your components. If you want to change the value of the state property, you must mutate it through mutations, which must be synchronous. If you want to make asynchronous calls to mutate the state, then you must use actions to commit the mutations by dispatching the actions in your components.

You have also learned that creating a store in Nuxt apps is easier and simpler than in Vue apps because Vuex is preinstalled on Nuxt by default. Also, in Nuxt, you do not need to assemble modules and all their methods manually because they are done for you by default. Additionally, in Nuxt, you can use the fetch and nuxtServerInit methods to populate the store state with a server-side API before rendering the page components and initiating your Nuxt app. Lastly, you have managed to create a simple user login app using a Vuex store with the nuxtServerInit action method, and this has paved the way for creating user logins and API authentication in the upcoming chapters.

In the next chapter, we will look at middleware in Nuxt – specifically, route middleware and server middleware. You will learn to differentiate between these two types of Nuxt middleware. You will create some route middleware with navigation guards in Vue apps, before making middleware in Nuxt apps. Then, you will write some Nuxt server middleware in the serverMiddleware configuration option as an alternative server API to the server-side API that you learned to create in Chapter 8, Adding a Server-Side Framework. Last but not least, you will learn how to use Vue CLI to create Vue apps as opposed to the Vue apps we have been making with the custom webpack configuration. So, let's get to it.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.138.178