Tame the Vuex Beast with vuex-pathify

Joshua Bemenderfer

As far as Flux architecture implementations, Vue.js’ Vuex is one of the simplest, but most elegant. However, it still could be better. Trying to remember to write getters and mutations for every property in your store seems like a bit of unneeded cognitive overhead. And why do we need to map getters, mutations, and actions manually? What was the difference between commit and dispatch again? vuex-pathify by Dave Stewart attempts to reduce all this mental overhead by supplying simple wrappers for Vuex functionality and relying on convention to reduce boilerplate.

This article assumes you have a Vue.js project with Vuex already set up, and that you know the basics of how it works. If not, take a look at our Vuex guide.

Install vuex-pathify

First things first, go ahead and install pathify and enable it in your base Vuex store configuration as a plugin.

$ npm install --save vuex-pathify

Then enable the plugin in your store.

main.js (example)

import Vue from 'vue';
import Vuex from 'vuex';
// If you want to configure pathify, this import will be different.
// See the "Configuring Pathify" section below.
import pathify from 'vuex-pathify';
import App from './App.vue';

Vue.use(Vuex);

// Usual Vuex stuff. Normally not in the main file.
const state = {};
const getters = {};
const mutations = {};
const actions = {};
const actions = {};

const store = new Vuex.Store({
  // Enable the vuex-pathify plugin.
  plugins: [pathify.plugin],

  state,
  getters,
  mutations,
  actions,
  modules,
});

new Vue({
  store,
  render: h => h(App)
}).$mount('#app');

Comparison

So the question now is… What does vuex-pathify do?

Normal vuex

To illustrate this, let’s take a look at a basic Vuex setup. Just a tiny form that lets you edit your first and last name with two-way data binding.

main.js

import Vue from 'vue';
import Vuex from 'vuex';
import App from './App.vue';

Vue.use(Vuex);

const state = {
  firstName: 'Richard',
  lastName: 'Gator'
};

// We need these to be able to access
// their values outside of the store module.
const getters = {
  firstName: state => state.firstName,
  lastName: state => state.lastName
};

// We use these to update the values of the store.
const mutations = {
  SET_FIRST_NAME: (state, firstName) => {
    state.firstName = firstName
  },
  SET_LAST_NAME: (state, lastName) => {
    state.lastName = lastName
  }
};

const store = new Vuex.Store({
  state,
  getters,
  mutations
});

Vue.config.productionTip = false;

new Vue({
  store,
  render: h => h(App)
}).$mount('#app');

App.vue

<template>
  <div id="app">
    <h2>Your Name Is: {{firstName}} {{lastName}}</h2>
    <form>
      <label>
        First Name
        <input type="text" v-model="firstName"/>
      </label>
      <label>
        Last Name
        <input type="text" v-model="lastName"/>
      </label>
    </form>
  </div>
</template>

<script>

export default {
  name: 'app',
  computed: {
    // We have to create computed getters and setters for both properties
    // to be able to use them in v-model.
    // Quite a pain for larger forms, wouldn't you say?
    firstName: {
      get() { return this.$store.getters.firstName },
      set(val) { this.$store.commit('SET_FIRST_NAME', val) }
    },
    lastName: {
      get() { return this.$store.getters.lastName },
      set(val) { this.$store.commit('SET_LAST_NAME', val) }
    }
  }
}
</script>

That’s… a lot of boilerplate for something so simple. You’ve got to create the state, add getters, mutations, and computed properties to tie them all together.

With vuex-pathify

Let’s look at the same thing with vuex-pathify.

main.js

import Vue from 'vue';
import Vuex from 'vuex';
import pathify from 'vuex-pathify';
import { make } from 'vuex-pathify';
import App from './App.vue';

Vue.use(Vuex);

const state = {
  firstName: 'Richard',
  lastName: 'Gator'
};

// You don't even need getters, pathify will use the store data directly!
// Though if you want, it can generate them for you with `make.getters(state)`

// Same for mutations and actions. (Uses the SET_* format, but this is configurable.)
const mutations = make.mutations(state);

const store = new Vuex.Store({
  // Don't forget the plugin!
  plugins: [pathify.plugin],
  state,
  mutations
});

Vue.config.productionTip = false;

new Vue({
  store,
  render: h => h(App)
}).$mount('#app');

App.vue

<template>
  <div id="app">
    <h2>Your Name Is: {{firstName}} {{lastName}}</h2>
    <form>
      <label>
        First Name
        <input type="text" v-model="firstName"/>
      </label>
      <label>
        Last Name
        <input type="text" v-model="lastName"/>
      </label>
    </form>
  </div>
</template>

<script>
import { sync } from 'vuex-pathify';

export default {
  name: 'app',
  computed: {
    // The sync helper creates two-way bindings for store data and mutations.
    firstName: sync('firstName'),
    lastName: sync('lastName'),
    // We could reduce boilerplate even further using:
    // ...sync(['firstName', 'lastName'])
  }
}
</script>

Looks like a significant reduction in redundant code to me!

The Mechanics

At the center of vuex-pathify is a path system and resolution algorithm. A path looks something like this: module/property@nested.sub.property

For example, with this path: data/user/firstName, pathify can determine that:

  • The referenced module is the user submodule of the data module.
  • The getter should be for data/user/firstName.
  • The relevant mutation would be data/user/SET_FIRST_NAME.
  • The relevant action would be data/user/setFirstName.

If you were to pathify.get('data/user/firstName'), pathify would first look for a getter in the user submodule of the data module that is firstName. Failing that, it would directly reference the data.user.firstName property on the store.

If you were to call pathify.set('data/user/firstName', 'Richard'), Pathify would first check for a setFirstName action in the relevant module. If it doesn’t exist, it would check for a SET_FIRST_NAME mutation and use that instead.

pathify.sync('data/user/firstName') allows you to combine both of those behaviors into a single computed property. You could use the @ syntax to access further sub-properties. For example, if you didn’t have a user module, but did have a userData property on the data module, you might use pathify.sync('data/userData@firstName') instead, to the same effect.

This allows you to access all Vuex features with a consistent and simple syntax, instead of having to resort to commit(), dispatch(), and the likes.

For more details on how to use vuex-pathify, see the official docs.

Configuring Vuex-Pathify

The above information should be enough to get you interested in, if not started with vuex-pathify, but there is one thing you should be aware of. Configuring the module is done in a slightly different way than normal. Instead of using options passed to the constructor, at runtime, vuex-pathify is configured by modifing properties on the prototype. The recommended way to configure the plugin is like so:

  1. Create a new file called pathify.js in your source directory.
  2. Import pathify and modify the configuration values.
  3. Re-export pathify and use it in your vuex module.

The full list of options you can modify is available here.

pathify.js (New File)

// Import pathify.
import pathify from 'vuex-pathify';

// Mapping: /thing -> getter: thing, mutation: SET_THING, action: setThing
options.mapping = 'standard'; // Default

// Re-export pathify.
export default pathify;

main.js

...
import pathify from './pathify'; // instead of from `vuex-pathify.
...
  Tweet It

🕵 Search Results

🔎 Searching...

Sponsored by #native_company# — Learn More
#native_title# #native_desc#
#native_cta#