Declarative Feedback on Vuex Actions through Vuex
Tags: vue, javascript, webdev, tutorial
Published: 2021-01-31 16:59:08

---
title: Declarative Feedback on Vuex Actions through Vuex
published: true
description: Simple, effective declarative feedback system.
tags: vue, javascript, webdev, tutorial
//cover_image: https://direct_url_to_image.jpg
---

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:

```vue
<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:

```vue
<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:
```js
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:

```js
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:
```js
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:

```js
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.