@lianulloa/vuex-module-maker

1.1.1 • Public • Published

VuexModuleMaker

It is just an helper to create Vuex modules. Its target is to reduce the amount of code needed to create a Vuex module and thus to keep code DRY

Quick Start

const state = {
  categoriesPlan: [],
  categoriesTrie: {},
  groupingCodes: []
}

const vuexModule = new VuexModuleMaker(
  {
    state,
    actions: {
      createCategory: {
        service: createCategoryAPI,
        attr: "category"
      },
      getGroupingCodes: {
        service: listGroupingCodesAPI,
        attr: "groupingCodes"
      }
    }
  }
).getModule()

This code creates a Vuex module following the mapping standard of vuex-pathify, for example:

state getter mutation action
foo foo SET_FOO setFoo

Hence, assuming this module is registered under the name of categories, then the next code is completely valid:

store.getters["categories/categoriesPlan"]

store.commit("categories/SET_GROUPING_CODES", groupingCodes)

store.dispatch("categories/createCategory", newCategory)

Complex Example

// usersApi is a class with methods to request info from API
import usersApi from "../../api/users"
import { VuexModuleMaker, actionConfigs } from "@lianulloa/vuex-module-maker"

const state = {
  users: [],
  totalUsers: 0
}

const vuexModule = new VuexModuleMaker({
  state,
  getters: {
    activeUsers: state => {
      return state.users.filter(user => user.status === "active")
    },
    coolUsers: state => {
      return state.users.filter(user => user.settings.cool)
    }
  },
  actions: {
    getUsers: {
      service: usersApi.list.bind(usersApi),
      attr: "users",
      cacheAPIRequestIn: "users/fetchUsers", // cache usersApi.list responses through this action. See vuex-cache
      ...actionConfigs.common.list
    },
    createCustomField: {
      service: usersApi.create.bind(usersApi),
      attr: "users",
      cacheActionToDelete: "users/fetchUsers",
      ...actionConfigs.common.create
    },
    editCustomField: {
      service: usersApi.edit.bind(usersApi),
      attr: "users",
      cacheActionToDelete: "users/fetchUsers",
      editingRefreshService: usersApi.detail.bind(usersApi),
      ...actionConfigs.common.edit
    },
    deleteCustomField: {
      service: usersApi.delete.bind(usersApi),
      cacheActionToDelete: "users/fetchUsers"
    }
  }
})

export default vuexModule.getModule()

Clearing cache in all open tabs

Available from version 1.1.0+
Your user can have two or more tabs of your app open in the browser, and these tabs will be using different instances of your store, thus different instances of the cache. So it could be useful, that when some part of the cache gets cleaned in one tab, that change gets reflected in all open tabs.

To enable this behavior, takes just two simple steps:

  1. Register the plugin createChannel in the store
import { createChannel } from "@lianulloa/vuex-module-maker"

export default new Vuex.Store({
  plugins: [ createChannel( {channelName:"any-name"} ) ],
  modules: {
    todos
  }
})
  1. Set the module name as an option to the VuexModuleMaker constructor
const state = {
  todos: []
}

const vuexModule = new VuexModuleMaker({
  state,
  actions: {
    getC: {
      service: list,
      attr: "todos",
      cacheAPIRequestIn: "todos/fetchTodos",
    },
    createC: {
      service: create,
      attr: "todos",
      cacheActionToDelete: "todos/fetchTodos",
    }
  }
}, { namespaced: true, moduleName: "todos" }) // Note the field moduleName

export default vuexModule.getModule()

The module name MUST match with how you register the module at the first step

That's it. Now whenever one part of the store's cache gets cleared, it will do the same in all the open tabs of your app

API

VuexModuleMaker is just a class which generate a Vuex module from a few configurations

Constructor

The constructor takes two parameters. The second is completely optional.

The fist is an object which defines the usual options: state, getters, mutations, actions

The second (and last) is an options object for Vuex. By default it look like this: { namespaced: true }

state keeps its usual syntax. The syntax for getters, mutations, and actions is described below

getters

  • type: Object.<string,function|string>
  • Optional

Example:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
  groupingCodes: []
}

const getters: {
  "tree": "categoriesTrie",
  "treeWithFunction": state => {
    return state.categoriesTrie
  }
}

These() two getters are equivalents.

This way a custom name can be defined for a getter and/or defined explicitly what is returned by using a funciton.

Note: This parameter is optional. Getters are defined automatically based on the fields of state

mutations

  • type: Object.<string,function|string>
  • Optional

Por ejemplo:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
  groupingCodes: []
}

const mutations: {
  "SET_CATEG_TRIE": "categoriesTrie",
  "SET_CATEG_TRIE_2": (state, payload) => {
    state["categoriesTrie"] = payload
  }
}

These() two mutations are equivalents.

This way a custom name can be defined for a mutation and/or defined explicitly what is setted by using a funciton.

Note: This parameter is optional. Mutations are defined automatically based on the fields of state

actions

  • type: Object.<string,Function| ActionConfig> Below you can see what is an ActionConfig
  • Required

Example:

const state = {
  category: null
  categoriesPlan: [],
  categoriesTrie: [],
  groupingCodes: []
}

const actions = {
  createCategory: {
    service: createCategoryAPI,
    attr: "category"
  },
  getCategories: (context, query) => {
    return new Promise(async (resolve, reject) => {
      try {
        const { data } = await categoriesApiSet.list(query)
        if (query.format === "plan") {
          context.commit("SET_CATEGORIES_PLAN", data)
          resolve(data)
        } else {
          context.commit("SET_CATEGORIES_TRIE", data)
          resolve(data)
        }
      } catch (error) {
        reject(error)
      }
    })
  }
}

For actions each key's value could be a function or an object with the config needed (See below). If a function is used, it MUST comply with the Vuex's action definition

If an object is used, it will generate an action which at least will:

  1. Execute the function passed as service field. Parameters received by the action (when it is dispatched) will be forwarded to this service
  2. Update state with the response from service

In order to update state, it will use field attr (or mutation if it is defined) In the example above, category would be updated when action createCategory is dispatched. On the other side, if mutation is defined, it will be used to update state through a context.commit call.

Example:

const actions = {
  createCategory: {
    service: createCategoryAPI,
    mutation: "SET_CATEGORY_BY_HELPER"
  }
}

will be the same as doing:

//...
const {data} = await createCategoryAPI()
context.commit("SET_CATEGORY_BY_HELPER", data)
//...

The field spreadServiceArgs (See below) controls if actions arguments (when dispatched) should be spreaded when forwarded down to service. Useful if service expect more than one argument.

if (actionConfig.spreadServiceArgs) {
  // if need to pass more than one parameter to service
  data = (await actionConfig.service(...requestBody)).data
} else {
  data = (await actionConfig.service(requestBody)).data
}

this way, it is possible to dispatch an action with an array of arguments de esta manera, es posible llamar a un action con un array de parámetros if necessary

this.actionToSetItems( [ item1, item2, item3 ] )
action.service( item1, item2, item3 )

Métodos

getModule

Creates the Vuex module

Example:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
  groupingCodes: []
}

const vuexHelper = new VuexModuleMaker(
  {
    state,
    actions: {
      createCategory: {
        service: createCategoryAPI,
        attr: "category"
      },
      getGroupingCodes: {
        service: listGroupingCodesAPI,
        attr: "groupingCodes"
      }
    }
  }
)

const store = new Vuex.Store({
  modules: {
    vuexModule: vuexHelper.getModule()
  }
})

addGetter

Adds a getter

@param {Object.<string,function|string>} getter

Example:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
  groupingCodes: []
}

const vuexHelper = new VuexModuleMaker(
  {
    state,
    actions: {
      createCategory: {
        service: createCategoryAPI,
        attr: "category"
      },
      getGroupingCodes: {
        service: listGroupingCodesAPI,
        attr: "groupingCodes"
      }
    }
  }
)

vuexHelper.addGetter({customGetterName: "groupingCodes"})
vuexHelper.addGetter({
  customGetterName: state => state.groupingCodes
  }
)

addMutation

Adds a mutation

@param {Object.<string,function|string>} mutation

Example:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
  groupingCodes: []
}

const vuexHelper = new VuexModuleMaker(
  {
    state,
    actions: {
      createCategory: {
        service: createCategoryAPI,
        attr: "category"
      },
      getGroupingCodes: {
        service: listGroupingCodesAPI,
        attr: "groupingCodes"
      }
    }
  }
)

vuexHelper.addMutation({customGetterName: "groupingCodes"})
vuexHelper.addMutation({
  customGetterName: (state, payload) => {
    state["categoriesTrie"] = payload
  }
)

addAction

Adds an action

@param {actionName, action} action

Example:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
  groupingCodes: []
}

const vuexHelper = new VuexModuleMaker(
  {
    state,
  }
)

vuexHelper.addAction({
  createCategory: {
    service: createCategoryAPI,
    attr: "category"
  }
})
vuexHelper.addAction({
  getCategories: (context, query) => {
    return new Promise(async (resolve, reject) => {
      try {
        //...
      } catch (error) {
        //...
      }
    })
  }
)

buildGetters

Generates the getters

Example:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
}

const vuexHelper = new VuexModuleMaker(
  //...
)

vuexHelper.buildGetters()
/*
{
  categoriesPlan: state => state.categoriesPlan
  categoriesTrie: state => state.categoriesTrie
}
*/

buildMutations

Generates the mutations

Example:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
}

const vuexHelper = new VuexModuleMaker(
  //...
)

vuexHelper.buildMutations()
/*
{
  SET_CATEGORIES_PLAIN: (state, payload) => state.categoriesPlan = payload
  SET_CATEGORIES_TREE: (state, payload) => state.categoriesTrie = payload
}
*/

buildActions

Generates the actions

Example:

const state = {
  categoriesPlan: [],
  categoriesTrie: [],
}

const vuexHelper = new VuexModuleMaker({
  actions:  {
    createCategory: {
      service: createCategoryAPI,
      attr: "category"
    }
  }
}
  
)

vuexHelper.buildActions()
/*
{
  createCategory: (context, argument) => new Promise(() => {
    // basic action logic
  })
}
*/

ActionConfig

The action configuration object

  • @typedef {Object} ActionConfig
  • @property {Function} service - Service used to obtain data
  • @property {String} attr - The attribute of the state which will contain the data (if any)
  • @property {String} [mutation] - Mutation to used to set data when it is not the default for the attr
  • @property {boolean} [spreadServiceArgs] - Whether action argument should be spreaded into the service
  • @property {boolean} [append] - Whether data should be appended to existing data in a list format if requestBody.append === true
  • @property {boolean} [appendAlways] - Whether data should be appended to existing data in a list format even though is not requested
  • @property {boolean} [editing] - Whether it is an editing action. CANNOT be used along with append or appendAlways
  • @property {Function} [editingRefreshService] - Service used to refresh an edited document. MUST be defined if requestBody.refresh
  • @property {boolean} [hasMetadata] - Whether response will included a metadata field. If metadata is true, then mutation MUST NOT be defined
  • @property {String} [cacheAPIRequestIn] - Name of an action, automatically created, to cache API resquest made through service
  • @property {boolean} [cacheActionToDelete] - Name of an action to delete from cache after the request to API. If it is not in cache, it does nothing

Dependencies

  • vuex-cache It MUST be installed by consumer in order to cache actions

Package Sidebar

Install

npm i @lianulloa/vuex-module-maker

Weekly Downloads

1

Version

1.1.1

License

MIT

Unpacked Size

115 kB

Total Files

12

Last publish

Collaborators

  • lianulloa