Efficiently understanding and using Nuxt + Vuex

Todd Baur
ITNEXT
Published in
8 min readJul 28, 2018

--

Nuxt.js is a great framework. I have really enjoyed its convention over configuration approach. In the JS world this is incredibly refreshing to see and a huge time saver while developing. With that leaves a bit of a gap in documentation and real-world usage from being so new. Without further delay, let’s dive into understanding how Nuxt and Vuex work well together.

Why do I need this in my app?

One of the tenants of functional programming is writing small functions that do one thing really well. We often go astray from this with tight deadlines, or a lot of pressure to fix a hot issue quickly. But at some point that comes back to haunt us, and creates an opportunity to revisit designs that no longer fill their purpose. Maybe more than once in our careers we find a variable being mutated as a side effect of another event or function call as the source of a bug?

Vuex provides a one-way highway for handling application states. A laypersons way to think about this is to say “managed global variables that don’t let you mutate them directly”. By the end of this I hope you will be armed with all the knowledge to scream “wrong!” to that statement.

To clear this up more, lets look at the architecture:

A colorful diagram of how Vuex works

Vuex is everything inside the dotted green box. You’ll see below we’ll have a state, a mutation, and some actions in our Vuex store. When you make a new file in Nuxt’s store folder, that file will likely have some or all of these things included inside it. When a component is rendering, it has access to Vuex state and in turn can be used for whatever that component needs to function. Once rendered, it can dispatch or mutate the store, therefore causing anything else using that store to update.

Modules vs Single Store

Honestly the official documentation is very confusing about which way one should approach configuring Vuex. It is my opinion if you have more that three API calls or state management items to handle the modular approach is required. Outside of quickly getting something working at the beginning of a Nuxt project the single-store approach becomes just as painful as using global variables.

Knowing that modules are the best way to go, the documentation suggests building an index.js file in the store directory. Unless you plan on configuring Vuex with some fancy plugins or changing its configuration, index.js should never exist in the store folder. Instead each ‘widget’ needs its own file.

State

State is a simple function that returns the object stored inside it. Typically I build something like store/cars.js

export const state = () => ({
list: [],
car: {}
})

This gives me a place to store my collection of cars, and a place to store the ‘current’ car.

In your component html you can then access it using $state.cars.list, and in your javascript via this.$state.cars.list. That is all fine if it is read-only. If you wanted to use that in a form, or use v-model then it won’t work. How annoying right? But that actually makes a lot of sense if you look at that picture above again. If you could change state directly, then you skip the parts that will let the rest of your app know it has changed. To get around using a deeply dot-notation path you want to use the mapState helper.

computed: {
...mapState({
cars: state => state.cars.list,
car: state => state.cars.car
})
}

It’s very important to keep your state as flat as possible. Deeply nested objects in a state lose reactivity. For example, if your state contains something like store.state.cars.car.color, mutations on color would need to be forced to reload. It is better to make a new store module just for a single car then. I tend to start out using the state.things.thing pattern and then refactor to state.thingwhen I see I have a need to do many operations on the thing. Then you state would contain the attributes for thing, restoring attributes to their reactive state. This is because if Vuex had to walk down a deep chain of object keys there is a huge performance hit, as well as a possibility of a never-ending loop.

Getters

Think of getters as computed properties for a store. They should read the current state of the module and return something. It should give some meaning to what it is expected to present. Meaning full names are very important!

totalCars: state => {
return state.cars.length
},
blueCars: state => {
return state.cars.filter(car => car.color === "blue")
}

Getters are always read-only values in your views. They can be used in combination with computed properties however, and you could provide a custom setter:

computed: {
blueCars: {
get() { return this.$getters['cars/blueCars'] },
set(car) { this.$store.dispatch('cars/paintBlue', car)
}
}

While there is a mapGetters helper, I’ve never had a huge need for it. Most of the time the components will do this work. That’s not saying don’t use it, just that its more likely to take a value from the store and alter it in a component. However if multiple components share reading these values and need to read updates, getters are a great choice.

Mutations

This is how to commit values in state. You don’t call it directly because a mutation is a reactive event and we would want our app to know when something changed.

export const mutations = {
set(state, cars) {
state.list = cars
},
add(state, value) {
merge(state.list, value)
},
remove(state, {car}) {
state.list.splice(state.list.indexOf(car), 1)
},
setCar(state, car) { state.car = car }
}

With this I have access to call $store.commit(‘cars/set’, [{id: 1, model: "Tacoma", brand: "Toyota"}]), etc. This is useful in components with the fetch, asyncData, and nuxtServerInit functions when you want to prefill the store with default values, or to use it as a set handler in a computed property.

In components you can use the mapMutations helper to quickly get mutator function calls mapped to your component:

methods: {
...mapMutations({
setCars: 'cars/set'
})
}

Which means in your component you can access it via this.setCars([{id: 1, model: "Tacoma", brand: "Toyota"}]).

I mentioned above about moving between state.things.thing to state.thing modules. In that case you would want to set up a mutation function that can easily map the object keys into the state. To do you would need something like:

setCar(state, form) {
let keys = Object.keys(form);
keys.forEach((key) => state[key] = form[key])
},

Of course you could validate your state here and prevent unexpected keys from polluting state as well. It all depends on app requirements.

I want to point out that mutating deeply nested values is also error prone and likely to cause Vuex to reject the mutation. Trying to mutate $store.state.cars.car.tire.brand would tell you it is not allowed. In that case, it would be best to create a car.js module that handles a single car, and therefore its state contains the attributes for a single car entity. Try to keep your state object as flat as possible. Create new stores when deep nesting mutations are used.

Actions

These are useful for any asynchronous activity the application needs. Especially useful for calling a back end server, or when non-blocking mutations are needed. Let me give an example for both scenarios.

export const actions = {
async get({commit}) {
await this.$axios.get('cars')
.then((res) => {
if (res.status === 200) {
commit('set', res.data)
}
})
},
async show({commit}, params) {
await this.$axios.get(`cars/${params.car_id}`)
.then((res) => {
if (res.status === 200) {
commit('setCar', res.data)
}
})
},
async set({commit}, car) {
await commit('set', car)
}
}

Here we defined three actions, get, show, and set. Get would fetch our entire car collection, and on success set the store with the list the server gives back. Show is the same, but we can pass in query parameters. This is useful for setting up searches, or any other scenario you can imagine where params are needed. Last is an addition onto our previous mutation, an action calling the mutation ‘set’ from above. Maybe we have a list of 15,000 cars and that is causing lag in the UI when committed? This way we can set up a successful future for using promises or async/await calls.

Plugins

Highly recommend reading the top 5 plugins used for Vuex. Simply create an store/index.js and import them there as recommended by their respective configurations.

Strict Mode

Nuxt by default configures Vuex to use strict mode in development. It can be disabled in store/index.js if you want, but doing so is a bad idea. Turning it off allows for mutation calls to come from anywhere, effectively breaking the rules of the architecture. In production it is disabled, but you’ve already written the app knowing the rules aren’t broken, so it becomes a performance boost when vuex no longer worries about checking for this problem in production.

A common error that happens is trying to mutate state using v-model=”$state.cars.car.color” which won’t work because this is effectively the read-only version of the value. Instead it should use either a computed property, or a combination of the mapState/mapMutations helpers.

Review

It took me a bit of unlearning some bad habits from my past Javascript code to really appreciate the value that Vuex brings to an application. I don’t think it is a required thing for every app; that would be narrow minded to believe that. There are many great implementations of state management in the JS world, but Nuxt has embraced Vuex and for now it works great.

Having a source of truth for state in a user interface saves a lot of time in development. Backing it with reactivity to changes, a consistent API, and great ecosystem of plugins gives us a toolbox to solve problems quickly.

In Nuxt

Nuxt provides a fetch method in pages. This is very useful for hooking into actions prior to page rendering. It’s common in pages to see a chunk of code like:

async fetch({store}) {
await store.dispatch("cars/get")
}

Which would call the ‘get’ action in the cars module, calling the back end and setting the cars state to the API response.

Vuex Helpers

Vuex has mapping helpers that make it easy to DRY the calls to the store. Instead of using $store.state... everywhere it is cleaner to use mapState. There are helpers for actions, mutations, and getters also.

computed: {
...mapState({
cars: state => state.cars.list
})
}

This creates a function call that lets us reference this.cars instead of this.$store.state.cars.list. Pretty clear which of those is much easier to read.

The mapGetters is using the exact same arguments as mapState, with the notable exception that it will not allow mutations. Getters are read-only and typically useful for formatting data.

What mapState is doing looks like this:

computed: {
cars: {
get() {
return this.$store.state.cars.list
},
set(val) {
this.$store.commit('cars/set', val)
}
}
}

This is also a useful pattern for when you need to perform custom mutations or any other pre-commit logic required. Remember though these should be very short functions so try to keep the logic in get/set minimal.

You can also use mapMutations to pull mutation functions into templates:

methods: {
...mapMutations('cars', ['set', 'anotherMutation'])
}

Then call this.set(val) instead of this.$store.commit('cars/set', val)

Conclusion

I hope this clears up a lot of the nuances and confusion about Vuex. It is such a great part of coding with Vue that knowing how to get the most out of it is a boost to productivity.

Edit: There is a follow up if you want more software patterns.

Reference:

Heads up: I wrote a follow up that talks about some useful snippets and design patterns for growing apps.

--

--