DEV Community

loading...

Working With Nuxt and Vuex

rlangvad profile image Rasmus Langvad Originally published at langvad.dev ・6 min read

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 = {}
Enter fullscreen mode Exit fullscreen mode

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')
    },
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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)
        }
    },
}
Enter fullscreen mode Exit fullscreen mode

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!

Discussion (3)

pic
Editor guide
Collapse
lawrencev profile image
lawrence-V

nice to see this thanks for this kind of aticle

Collapse
alexandrel0pes profile image
Alexandre Lopes

Thanks for this article!!! I'm studing vuex and was a great introduction.

Collapse
rlangvad profile image
Rasmus Langvad Author

Nice to hear!