How to use vue-resource ($http) and vue-router ($route) in a vuex store?

Benjamin Smith Max picture Benjamin Smith Max · Mar 2, 2017 · Viewed 8.5k times · Source

Before I was getting movie detail from the component's script. The function first check whether the movie ID of the store is same as of the route's param movie ID. If its same then don't get the movie from the server API, or else get the movie from the server API.

It was working fine. But now I am trying to get the movie details from the store's mutation. However I am getting error

Uncaught TypeError: Cannot read property '$route' of undefined

How to use vue-router ($route) to access the params and vue-resource ($http) to get from the server API in vuex store?

store.js:

export default new Vuex.Store({
    state: {
        movieDetail: {},
    },
    mutations: {
        checkMovieStore(state) {
            const routerMovieId = this.$route.params.movieId;
            const storeMovieId = state.movieDetail.movie_id;
            if (routerMovieId != storeMovieId) {
                let url = "http://dev.site.com/api/movies/movie-list/" + routerMovieId + "/";
                this.$http.get(url)
                    .then((response) => {
                        state.movieDetail = response.data;
                    })
                    .catch((response) => {
                        console.log(response)
                    });
            }
        },
    },
});

component script:

export default {
    computed: {
        movie() {
            return this.$store.state.movieDetail;
        }
    },
    created: function () {
        this.$store.commit('checkMovieStore');
    },
}

Answer

Amresh Venugopal picture Amresh Venugopal · Mar 3, 2017

To use $http or $router in your vuex store, you would need to use the main vue instance. Although I don't recommend using this, I'll add what I recommend after answering the actual question.


In your main.js or wherever you are creating your vue instance like:

new Vue({ 
  el: '#app',
  router,
  store,
  template: '<App><App/>',
  components: {
    App
  }
})

or something similar, you might also have added the vue-router and vue-resource plugins too.

Doing a slight modification to this:

export default new Vue({ 
  el: '#app',
  router,
  store,
  template: '<App><App/>',
  components: {
    App
  }
})

I can now import it in vuex stores like so:

//vuex store:
import YourVueInstance from 'path/to/main'

checkMovieStore(state) {
const routerMovieId = YourVueInstance.$route.params.movieId;
const storeMovieId = state.movieDetail.movie_id;
if (routerMovieId != storeMovieId) {
  let url = "http://dev.site.com/api/movies/movie-list/" + routerMovieId + "/";
  YourVueInstance.$http.get(url)
    .then((response) => {
       state.movieDetail = response.data;
     })
     .catch((response) => {
       console.log(response)
     });
  }
}

and as the answer by Austio goes, this method should be an action as mutations are not designed to handle async.


Now coming to the recommended way of doing it.

  1. Your component can access the route params and provide it to the action.

    methods: {
      ...mapActions({
        doSomethingPls: ACTION_NAME
      }),
      getMyData () {
        this.doSomethingPls({id: this.$route.params})
      }
    }
    
  2. The action then makes the call through an abstracted API service file (read plugins)

    [ACTION_NAME]: ({commit}, payload) {
       serviceWhichMakesApiCalls.someMethod(method='GET', payload)
         .then(data => {
            // Do something with data
         })
         .catch(err => {
            // handle the errors
         })
    }
    
  3. Your actions do some async job and provide the result to a mutation .

    serviceWhichMakesApiCalls.someMethod(method='GET', payload)
         .then(data => {
            // Do something with data
            commit(SOME_MUTATION, data)
         })
         .catch(err => {
            // handle the errors
         })
    
  4. Mutations should be the only ones to modify your state.

    [SOME_MUTATION]: (state, payload) {
       state[yourProperty] = payload
    }
    

Example A file which contains a list of endpoints, you might need it if you have different stages of deployment which have different api endpoints like: test, staging, production, etc.

export const ENDPOINTS = {
  TEST: {
    URL: 'https://jsonplaceholder.typicode.com/posts/1',
    METHOD: 'get'
  }
}

And the main file which implements Vue.http as a service:

import Vue from 'vue'
import { ENDPOINTS } from './endpoints/'
import { queryAdder } from './endpoints/helper'
/**
*   - ENDPOINTS is an object containing api endpoints for different stages.
*   - Use the ENDPOINTS.<NAME>.URL    : to get the url for making the requests.
*   - Use the ENDPOINTS.<NAME>.METHOD : to get the method for making the requests.
*   - A promise is returned BUT all the required processing must happen here,
*     the calling component must directly be able to use the 'error' or 'response'.
*/

function transformRequest (ENDPOINT, query, data) {
  return (ENDPOINT.METHOD === 'get')
      ? Vue.http[ENDPOINT.METHOD](queryAdder(ENDPOINT.URL, query))
      : Vue.http[ENDPOINT.METHOD](queryAdder(ENDPOINT.URL, query), data)
}

function callEndpoint (ENDPOINT, data = null, query = null) {
  return new Promise((resolve, reject) => {
    transformRequest(ENDPOINT, query, data)
      .then(response => { return response.json() })
      .then(data => { resolve(data) })
      .catch(error => { reject(error) })
  })
}

export const APIService = {
  test () { return callEndpoint(ENDPOINTS.TEST) },
  login (data) { return callEndpoint(ENDPOINTS.LOGIN, data) }
}

The queryAdder in case it is important, I was using this to add params to the url.

export function queryAdder (url, params) {
  if (params && typeof params === 'object' && !Array.isArray(params)) {
    let keys = Object.keys(params)
    if (keys.length > 0) {
      url += `${url}?`
      for (let [key, i] in keys) {
        if (keys.length - 1 !== i) {
          url += `${url}${key}=${params[key]}&`
        } else {
          url += `${url}${key}=${params[key]}`
        }
      }
    }
  }
  return url
}