In this article I will show you how to get started with Vuex in a Nuxt application. How to set up your first Vuex module and how you can use the store in your components and pages. I will not be going in to details about how Vuex works. I will focus on how to use Vuex within a Nuxt application and how easy it is to get started with.
What is Vuex
The most simple explanation is that Vuex is a state management tool for Vue. It holds a global state that can be accessed and modified within your application. It enables you to work with state in a controlled way and to use conventions like actions
and mutations
to handle changes to the state.
It is a separate module within the Vue ecosystem maintained by Vue core members and the community.
You do not have to use Vuex in a Vue application but as soon as you need some global state or a centralised place to hold data, it is recommended to use Vuex. If you want to know more about Vuex in detail I recommend you to head over to the Vuex documentation for further reading.
How do I set up Vuex in a Nuxt application
Vuex comes along with Nuxt by default. When scaffolding a new project you might have noticed an empty folder called store
. This is where we work with Vuex. It is disabled by default but will be activated as soon as a .js
file is created in this folder. Each file in this folder will be converted to a Vuex module. The index.js
file will be the root module.
The four building blocks of a Vuex module:
// store/index.js
export const state = () => {}
export const mutations = {}
export const actions = {}
export const getters = {}
Lets say we want a specific Vuex module to handle fruits in our application. Yes lets work with fruits. Why not. It's a great metaphor.
Create a new .js
file in the store folder called store/fruits.js
and add some methods.
// store/fruits.js
export const state = () => ({
fruits: [],
})
export const mutations = {
addFruit(state, fruit) {
state.fruits.push(fruit)
},
removeFruit(state, fruitId) {
state.fruits = state.fruits.filter((fruit) => fruit.id === fruitId)
},
}
export const actions = {
addFruit(context, fruit) {
const slicedFruit = sliceFruit(fruit)
context.commit(slicedFruit)
},
}
export const getters = {
getApples: (state) => {
return state.fruits.filter((fruit) => fruit.type === 'Apple')
},
}
Here we are using both a mutation and an action to handle adding a new fruit. Actions can be used when you want to change the incoming data or make an asynchronous action before committing the data to the store. In this example we are calling a method that will slice the fruit before adding it. In my opinion when having an action that only forwards the data to the mutation, that action can be omitted and you should use the mutation.
We have also created a getter that will filter the list of fruits in the state to only return a list of apples. This is useful since the getter will be reactive. So when the fruit list updates the getter will reflect these changes.
So how are we using this new fresh fruits module in our application?
Working with the Vuex state from pages and components
Using Nuxt context store helper
Accessing the state
Nuxt adds a store property to the context that can be accessed with this.$store
in every Vue component. So if we want to display our list of fruits we could do something like this:
<template>
<ul>
<li v-for="fruit in fruits" :key="fruit.id">
{{ fruit.name }} - {{ fruit.type }}
</li>
</ul>
</template>
<script>
export default {
computed: {
fruits() {
return this.$store.fruits.state.fruits
},
},
}
</script>
Notice that we need to access the state from the module name on the $store
property. If we would have added our state to the index file we could have accessed it directly on $store.state.fruits
. We're using a computed property to make it reactive.
Using actions and mutations
So how do we use the actions and mutations to add a new fruit? I've hard coded the fruit to be added just for demo purposes.
<script>
export default {
methods: {
addFruitFromComponent() {
const newFruit = {
name: 'Pink Lady',
type: 'Apple',
id: 2,
}
// Use dispatch to call an action
this.$store.dispatch('fruits/addFruit', newFruit)
// Use commit to call a mutation
this.$store.commit('fruits/addFruit', newFruit)
},
},
}
</script>
The actions and mutations are available directly on the $store
property. There are two differents methods to use: dispatch
and commit
. Dispatch triggers an action and commit is used for mutations. In the example above both will add a new fruit to the fruits list but the action will first call the sliceFruit(...) method. If you need to manipulate, change or validate the data being added, this should be done in the action rather than the mutation method. Or maybe even before being sent to the store at all, but that's another discussion. The important thing to remember is that actions can handle asynchronous calls. While mutations are synchronous and should only be used to update the state.
Using getters
The getter methods works very similar to a computed property. With the difference being accessible globally. Using our getApples
getter would look something like this:
<template>
<ul>
<li v-for="apple in apples" :key="apple.id">
{{ apple.name }}
</li>
</ul>
</template>
<script>
export default {
computed: {
apples() {
return this.$store.fruits.getters.getApples()
},
},
}
</script>
Using Vuex mappers
Vuex also provides mappers that you can use instead of this.$store
. For a more detailed explanation on how to use them I once again refer to the Vuex documentation and especially the section for binding helpers.
Taking the same example as above but with binding helpers would look like this:
<template>
<ul>
<li v-for="fruit in fruits" :key="fruit.id">
{{ fruit.name }} - {{ fruit.type }}
</li>
</ul>
</template>
<script>
import { mapState } from "vuex";
export default {
computed: {
...mapState('fruits', ['fruits']
}
}
</script>
Using actions and mutations:
<script>
import { mapActions, mapMutations } from 'vuex'
export default {
methods: {
...mapMutations('fruits', ['removeFruit']),
...mapActions('fruits', ['addFruit']),
handleFruits() {
const newFruit = {
name: 'Pink Lady',
type: 'Apple',
id: 2,
}
// Call action to add fruit
this.addFruit(newFruit)
// Call mutation to remove fruit
this.removeFruit(newFruit.id)
},
},
}
</script>
One benefit of using the binding helpers is that you can call the methods with less code. This is extra useful when calling an action or mutation directly in the template.
<!-- With binding helpers -->
<template>
<button @click="addFruit(fruit)" />
</template>
<!-- With $store helper -->
<template>
<button @click="$store.addFruit(fruit)" />
</template>
One downside is that it might loose the visual connection to the store. It's not obvious if this.addFruit
or addFruit
is referring to the store action or a method on the component. Try them out and decide what works best for you. In larger projects you will most likely encounter both ways of doing this. This could be dangerous and I would recommend that you always strive for consistency. Find out how you or your team prefers to do things and apply that in all places.
Using the nuxtServerInit method
Nuxt exposes a specific store action that lets you set a predefined state from the server that synchronizes with the client. This requires that Nuxt runs in universal mode and that the nuxtServerInit
action is present in the index.js
file.
Example taken directly from the nuxtServerInit documentation.
// store/index.js
export const actions = {
nuxtServerInit({ commit }, { req }) {
if (req.session.user) {
commit('user', req.session.user)
}
},
}
Upcoming updates
With the recent release of Vue 3 both Nuxt and Vuex have new major versions around the corner. Although they are not released yet and any breaking changes are not final. But keep in mind that this article focuses on Nuxt 2 in combination with Vuex 3. I will write a new article later on with Nuxt 3 and Vuex 4 in focus. Since great things are coming! But many applications will use these versions for a long time and it's important to understand these concepts as well.
Conclusion
Working with Vuex in a Nuxt application is simple and easy to get started with. It provides module creation based on folder and file structure under the store
folder. The context helper property $store
is useful when accessing the store from a component. Other than that there is no Nuxt specific ways of working with Vuex compared to a plain Vue application. Nuxt let you quickly get up and running so you can focus on implementation rather than configuration.
Thanks for reading! Please reach out to me if you have any questions or thoughts on this article.
Happy coding!
Top comments (3)
nice to see this thanks for this kind of aticle
Thanks for this article!!! I'm studing vuex and was a great introduction.
Nice to hear!