Declarative Feedback on Vuex Actions through Vuex
Muhammad Talha Akbar
Posted on January 31, 2021
As a frontend developer, we come across many a times the scenario where we dispatch actions and in our components, have status "flags" that track whether that action is being processed aka. loading, succeeded or failed. And, then show an appropriate feedback to the user based on these flags. Take this Vue component with Vuex store as an example:
<template>
<div>
<message v-if="error">Could not do something.</message>
<message v-if="success">Did something successfully.</message>
<button @click="doSomething()" :disabled="loading">
<spinner v-if="loading"></spinner> Do Something
</button>
</div>
</template>
<script>
export default {
data() {
return {
error: false,
success: false,
loading: false
}
},
methods: {
async doSomething() {
this.loading = true;
try {
await this.$store.dispatch('someAction');
this.success = true;
} catch(e) {
this.error = true;
}
}
}
}
</script>
Above, you can see, we have a simple, effective feedback state. However, it's repetitive and only available inside the component itself. What if we could make this tracking of action's state declarative, available globally and with almost no boilerplate? What if it was something like this:
<template>
<action-status :actionName="someAction">
<div slot-scope="{ status }">
<message v-if="status.error">Could not do something.</message>
<message v-if="status.success">Did something successfully.</message>
<button @click="doSomething()" :disabled="status.loading">
<spinner v-if="status.loading"></spinner> Do Something
</button>
</div>
</action-status>
</template>
<script>
export default {
methods: {
async doSomething() {
await this.$store.dispatch('someAction');
}
}
}
</script>
Do you like the new way: a single neat component that takes the name of the Vuex action you want to observe and provides you with its status? If yes, here's how you can develop the action-status
component:
The first step is to hook into our Vuex store and get updated about every action that is ever dispatched. To do that, you have store.subscribeAction
method available. It takes an object with 3 callbacks i.e. before
, after
and error
. So, register all 3 callbacks with something like:
store.subscribeAction({
before(action) { },
after(action) { },
error(action) { }
});
The second step is develop a store module named actionStatus
which will store the status of every action that is being dispatched. Here's how actionStatus
module will look like:
export default {
namespaced: true,
state: {
actions: [],
statusAction: {}
},
getters: {
status: state => actionName => {
return state.statusAction[actionName] || {
error: false,
success: false,
loading: false
}
}
},
mutations: {
NEW: (state, action) => {
if(!state.statusAction[action.type]) {
state.actions = [...state.actions, action.type];
}
state.statusAction = {
...state.statusAction,
[action.type]: {
loading: true,
error: false,
success: false
}
}
},
SUCCESS: (state, action) => {
state.statusAction = {
...state.statusAction,
[action.type]: {
loading: false,
error: false,
success: true
}
}
},
ERROR: (state, action) => {
state.statusAction = {
...state.statusAction,
[action.type]: {
loading: false,
error: true,
success: false
}
}
},
},
actions: {
trackNew: ({ commit }, action) => {
commit('NEW', action);
},
trackSuccess: ({ commit }, action) => {
commit('SUCCESS', action);
},
trackError: ({ commit }, action) => {
commit('ERROR', action);
}
},
}
The third step will be to dispatch actionStatus
actions inside our store.subscribeAction
hook:
function isActionStatusAction(action) {
return action.type.indexOf('actionStatus) > -1;
}
store.subscribeAction({
before(action) {
if(!isActionStatusAction(action)) {
store.dispatch('actionStatus/trackNew', action);
}
},
after(action) {
if(!isActionStatusAction(action)) {
store.dispatch('actionStatus/trackSuccess', action);
}
},
error(action, status, error) {
// `error` is available too
if(!isActionStatusAction(action)) {
store.dispatch('actionStatus/trackError', action);
}
}
});
The fourth step is create the action-status
component that gets the status data about Vuex actions from the actionStatus
module and makes it available to be used in any Vue component:
export default {
props: {
actionName: {
type: String,
required: true
}
},
render() {
return this.$scopedSlots.default({
status: this.$store.getters['actionStatus/status'](this.actionName)
});
}
}
And, with these 4 steps, we can now say goodbye status flags inside of our components and track any Vuex action from any Vue component. You can modify the above code to make the actual error object available in the slot-scope
, or modify it to accept multiple action names and report the status of each Vuex action inside your component. The possibilities are there.
Finally, do you think it's something you will end up using in your project? Look forward to hear your feedback about this.
Posted on January 31, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.