[Vuejs]-Vue JS change submit button if errors

1đź‘Ť

The question is about using ...mapMutations, but in case someone want to add business logic, mapAction and mapState would be recommended. I will explain how to make it work with mapAction and mapState since calling API might involve using business logic within your application. Otherwise, I would say, why do you even bother using VueX except for notifying other part of your application that you are loading ;). That being said, here’s my answer.

Using the ...mapState you have what you would be searching for, the computed reactivity of the state. This would happen especially during the invoke of the action. The action would then be changing, or what we call commit in VueX, the state (See doc: https://vuex.vuejs.org/guide/state.html)

Let’s take your code and change it into a module with a namespace and then use the module in your vue (This is what I would do if the application is big, otherwise the same can be achieved using the mutation or no VueX at all):

const LOADING_STATE = 'LOADING_STATE'
export default {
  namespaced: true, // Read the doc about that

  state: {
    loaded: false
  },

  mutations: {
    [LOADING_STATE]: function (state, isLoading) {
      state.loading = isLoading
    }
  },

  actions: {
    setLoading ({ commit }, isLoading) {
      commit(LOADING_STATE, isLoading)
    }
  }
}

For your vue file where you have your template and your actions. It would look like this:

<script>
  import { mapAction, mapState } from 'vuex'

  exports default {
     computed: {
        ...mapState({
          // Here you could even have the full computation for the CSS class.
          loading: state => state.loadingModule.loading,

          // Like this... or you could use the getters that VueX does (search in the documentation since it's out of the scope of your question)
          loadingCss: state => { return state.loadingModule.loading ? 'is-loading' : '' }
        })
      },
     methods: {
         // Usage of a namespace to avoid other modules in your VueX to have the same action.
         ...mapActions(['loadingModule/setLoading']),
     }
  }
</script>

And regarding your html template, you will be able to call the method this['loadingModule/setLoading'](true) or false and then the property that you can react to will be “loading”.

While using promises, during your post or get or any other HTTP rest call, don’t forget the context. If you’re using Axios, after registering it in your VueJs context, I would do

this.$http.get('/my/api')
   .then(response => { 
      // ... some code and also set state to ok ... 
   })
   .catch(e => { 
      // set state to not loading anymore and open an alert 
   })

Let’s complete your code now considering you’re doing your HTTP(S) call somewhere.

<form @submit.prevent="postThis">

  <div class="field">
    <div class="control">
      <!-- Here I would then use a computed property for that class (error). I would even put the a template or a v-if on a div in order to show or not all those html elements. That's you're choice and I doubt this is your final code ;) -->
      <input class="input" :class="{ 'is-danger': errors.title }" type="text" id="title" placeholder="I have this idea to..." autofocus="" v-model="newTitle">
    </div>

    <p class="is-size-6 help is-danger" v-if="errors.title">
      {{ errors.title[0] }}
    </p>
  </div>

  <div class="field">
    <div class="control">
      <button @click="['loadingModule/setLoading'](true)" type="submit" :class="{'is-loading' : loading }">
        Post
      </button>
    </div>
  </div>
</form>

0đź‘Ť

First, there is no need to have locally only needed state (loading) in global state (Vuex). So, typical usage looks like this:

<template>
  <form>
    <div class="field">
      <div class="control">
        <input
          class="input" :class="{ 'is-danger': errors.title }"
          type="text"
          id="title"
          placeholder="I have this idea to..."
          autofocus=""
          v-model="newTitle"
        >
      </div>
      <p class="is-size-6 help is-danger" v-if="errors.title">
        {{ errors.title[0] }}
      </p>
    </div>
    <div class="field">
      <div class="control">
        <button
          @click="postForm"
          :class="{'is-loading': isLoading }"
        >
          Post
        </button>
      </div>
    </div>
  </form>
</template>

<script>
export default {
  ...
  data () {
    return {
      ...
      newTitle: '',
      isLoading: false,
      response: null
    }
  },

  methods: {
    async postForm () {
      try {
        this.isLoading = true // first, change state to true
        const { data } = await axios.post('someurl', { title: this.newTitle }) // then wait for some async call
        this.response = data // save the response
      } catch(err) {
        // if error, process it here
      } finally {
        this.isLoading = false // but always change state back to false
      }
    }
  }
}
</script>

-1đź‘Ť

if you using vuex like this. I guess you misunderstood vuex. Because you can use for local variable and you can check api result. if you want seperate api request, you have to mapAction in methods and mapGetters in Computed

Leave a comment