Posted in Vue 8 Aug 2018 @ 07:22 PM~4 min read

Vuex State Helpers

Shorter code is great, in almost every situation. Even shorter code is amazing.

In a Vuex mutation, we normally pass the state and payload through a function that handles the state-change required. But if you’re anything like me, it can be done in a much cleaner way. Yes, there are indeed packages for this, like Pathify, though these can sometimes be a little much, especially when we want to achieve a small sub-set of cleanliness’.

Here’s how it’s normally done:

let mutations = {
  setName(state, name) {
    state.name = name
  },
  setPassword(state, password) {
    state.password = password
  },
}

Sure, that’s neat enough for the job,” they said. But what about repetition? We could always use arrow functions for this, which cleans things up considerably:

let mutations = {
  setName: (state, name) => state.name = name
  setPassword: (state, password) => state.password = password
}

But perhaps, it’s not as easy to read as we’d like it to be. And, the reptition is still there.

So let’s take another approach, and pass by method-reference. Almost like a method-access Vuex getter. If not, why not?

let mutations = {
  name: set('name'),
  password: set('password')
}

To achieve this, we’ll need to create a utility/​helper that gets us going. In the one I’ve put together, Lodash is being used to do the heavy lifting. The primary reason for choosing it was so that dot notation could be used easily. That said, this article is about the idea, and so you could use whatever tools you have to accomplish the same thing, if you really wanted to.

import _set from 'lodash/set'
import _get from 'lodash/get'
import _isArray from 'lodash/isArray'
import _findIndex from 'lodash/findIndex'

/**
 * Set a piece of state.
 * @param {String} key
 * @param {null|any} override
 */
export let set = (key, override = null) => (state, value) => {
  _set(state, key, override || value)
}

/**
 * Push a value to an array in state.
 * @param {String} key
 */
export let push = key => (state, value) => {
  let piece = _get(state, key)
  if (_isArray(piece)) {
    piece.push(value)
  }
}

/**
 * Remove a value from an array in state.
 * @param {String} key
 */
export let remove = key => (state, value) => {
  let piece = _get(state, key)
  if (_isArray(piece)) {
    let index = _findIndex(piece, value)
    piece.splice(index, 1)
  }
}

We could add more methods here, but these are the basic ones that cover a good amount of needs.

Note: I’m prefixing the imports with an underscore for the purposes of preventing naming collisions, and identifying the source of the import (it’s coming from Lodash, so an underscore is appropriate.)

Right, so how do we use them? As above, we pass the mutating function by reference using the utility/​helper we import. Go ahead and save the above to a file called, say, state-helpers.js. Then, import the methods you need into your store module:

import { set } from './state-helpers'

let state = {
  name: '',
  profile: {
    mobile: '',
  }
}

let mutations = {
  name: set('name'),
  mobile: set('profile.mobile')
}

let module = {
  namespaced: true,
  state,
  mutations
}

export default module

Note: Because we’re using Lodash, we can easily use dot notation to reference a nested piece of state, and still keep the code clean and easy to understand.

Here’s a simple example of how this can be used in a component:

import { mapMutations } from 'vuex'

export default {
  methods: mapMutations('namespace', ['name', 'mobile'])
}

Now, the values passed to name and mobile will be further passed through the assigned helper (set) which then performs the mutation when called.

So what if we want to set a mutation that clears something (ie, with a default)? A common example of this would be setting and clearing error messages. My preferences differ here (sometimes), but there’s no harm is using a clearErrors mutation in an action:

let mutations = {
  clearErrors: set('errors', {})
}

let actions = {
  saveSomething: ({ state, commit }) => new Promise((resolve, reject) => {
    // make an API call; then when it succeeds:
    commit('clearErrors')
    resolve()
  })
}

This works because our helper passes an empty object to the mutating method, which checks to see if an override exists. If it does, use it. If not, use the value passed by reference.

The push and remove helpers work in the exact same way as set, though they don’t support overrides. For pushing to an array, it would be as simple as accounting for the override in the helper-method’s signature and in the called method, as shown in the set example.

That’s all for now – hope this helps speed up your development-flow!