Designing the actions in Vuex modules may be pretty messy and there might be a lot of code duplication.
JavaScript does not provide many options for code reusability, but there are certainly some features of JavaScript which are different from other languages and may prove helpful.
In my vuex store design, I used the feature of JavaScript which creates a lot of trouble for developers especially me, which is, In JavaScript Objects are always passed by reference. I used it for my advantage.
I created a VuexUtil Module.
Utils/VuexUtils.js
export default {
getRequestWrapper(config) {
return this.requestWrapper(config, 'get')
},
postRequestWrapper(config) {
return this.requestWrapper(config, 'post')
},
postRequestWithMediaWrapper(config) {
// making this separate function can be useful in making progress bar and other things
return this.requestWrapper(config, 'post_with_media')
},
requestWrapper(config, type) {
/*
This is a special wrapper to abstract the requests inside vuex actions
This wrapper takes all the necessary resources within the config variable and makes the action on them,
This can take mutation names and corresponding response parameter and commit them,
/
*/
if (config.fetcher == null || config.url == null) {
throw new Error('Some Parameters were Missing')
}
if (!config.context.state.loading) {
return new Promise((resolve, reject) => {
// using return is essential here to use that method in asyncData function
if (!config.context.state.loading) {
config.context.commit('SET_LOADING', true, { root: true })
let request
if (type === 'get') {
request = config.fetcher.get(config.url, {
params: config.params,
})
} else if (type === 'post_with_media') {
request = config.fetcher.post(config.url, config.params, {
headers: {
'Content-Type': 'multipart/form-data',
},
})
} else if (type === 'post') {
request = config.fetcher.post(config.url, config.params)
} else {
throw new Error('Request type unknown')
}
// console.log(config.commitableMutations)
request
.then((response) => {
if (!response) {
reject(new Error('Response is Undefined, Not sure why'))
}
if (!response.data.success) {
reject(response.data.message)
}
if (config.commitableMutations.length) {
for (const mutation of config.commitableMutations) {
for (const property in mutation) {
// iterating over object properties
// just to satify IDE
if (
Object.prototype.hasOwnProperty.call(mutation, property)
) {
// console.log(`${property}: ${mutation[property]}`)
// eslint-disable-next-line no-eval
if (eval(mutation[property]) != null) {
let commitValueString
if (mutation[property] == null) {
commitValueString = 'response.data.result'
} else {
commitValueString = mutation[property]
}
// eslint-disable-next-line no-eval
config.context.commit(
'modules/' + config.module + '/' + property,
// eslint-disable-next-line no-eval
eval(commitValueString),
{ root: true }
)
if (config.callback) {
config.callback()
}
} else {
config.context.commit(
'modules/' + config.module + '/' + property,
// eslint-disable-next-line no-eval
[],
{ root: true }
)
}
}
}
}
}
resolve(response.data)
})
.catch((error) => {
reject(error)
})
.finally(() => {
config.context.commit('SET_LOADING', false, { root: true })
})
}
})
}
},
configAdapter({ config, getters, includeQuery }) {
// includeQuery defines if we need to include query params such as page and sort etc
// used in vuex actions
if (typeof config === 'undefined') {
config = {}
let essentialParams
if (typeof includeQuery !== 'undefined' && includeQuery === false) {
} else if (typeof getters.getQuery !== 'undefined') {
essentialParams = getters.getQuery
}
config.allParams = { ...essentialParams }
} else if (
typeof config.formData !== 'undefined' &&
config.formData instanceof FormData
) {
// backward compatiabiltiy
if (typeof includeQuery !== 'undefined' && includeQuery === false) {
} else if (typeof getters.getQuery !== 'undefined') {
Object.entries(getters.getQuery).map((item) => {
// eslint-disable-next-line no-eval
config.formData.append(item[0], item[1])
})
}
config.queryParams = config.formData
config.allParams = config.queryParams
} else if (typeof config === 'string') {
const urlAsConfig = config
config = {}
config.url = urlAsConfig
} else {
config.allParams = config.queryParams
let essentialParams
if (typeof includeQuery !== 'undefined' && includeQuery === false) {
} else if (typeof getters.getQuery !== 'undefined') {
essentialParams = getters.getQuery
}
config.allParams = { ...essentialParams, ...config.queryParams }
}
return config
},
}
the main purpose of this module is to provide the functions which are responsible for making the ajax requests, committing the mutations.
store/index.js
// state mutations and actions defined here will get added to every module file
// store gets automaticly created form modules
// https://nuxtjs.org/guide/vuex-store#modules-mode
export const state = () => ({
loading: false,
})
export const mutations = {
SET_LOADING: (state, value) => (state.loading = value),
}
export const actions = {
}
export const getters = {
getLoadingState: (state) => state.loading,
}
Here is how I am using these utility functions.
store/modules/experiences.js
import VuexUtils from '~/utils/VuexUtils'
const state = () => ({
// Pre-fill one row with an
// empty `Contact` model.
experience: {},
query: {
page: 1,
},
})
const getters = {
getExperience: (state) => state.experience,
}
const mutations = {
SET_EXPERIENCE: (state, payload) => {
state.experience = payload
},
}
const actions = {
submitExperienceAction(context, config) {
config = VuexUtils.configAdapter({
config,
getters: context.getters,
includeQuery: false,
})
const defaultValues = {
url: '/experiencess',
queryParams: {},
allParams: {}, // also includes additional params such as page no. sort etc
}
config = Object.assign(defaultValues, config)
return VuexUtils.postRequestWithMediaWrapper({
fetcher: this.$axios,
url: config.url,
module: 'experience',
context,
params: config.allParams,
commitableMutations: [{ SET_EXPERIENCE: 'response.data.result' }],
})
},
}
export default {
actions,
mutations,
state,
getters,
}
You will notice that I am returning the promise from vuex actions, Purpose of returning promise here is, I can also chain the response from action dispatch like below
this.$store
.dispatch('modules/auth/switchAccount', {
queryParams: {
new_account_type: newAccountType,
},
})
.then((response) => {
if (response.success) {
this.$buefy.toast.open({
message: response.message,
type: 'is-success',
})
// alert('status changed success fully')
}
})
.catch((error) => {
})
.finally(function () {
})
I also created a gist for it, https://gist.github.com/bawa93/64752137a6d6ab2e6c411c9629d44a66
Top comments (0)