2

So, I'm trying to make a request with axios in my main.js file.

I'm using, as shown, vue-router to make this request before each component is loaded. However, I'm not able to get this to work when my webpage is loaded for the first time. I mean, axios request is done after the component is loaded. Then, this is going to fail:

mounted() {
    if (Vue.prototype.$user.role == "Owner") {
      this.isOwner = true;
      this.estancoId = Vue.prototype.$user.estanco;
    }
  },

It shows me this error on the console log:

[Vue warn]: Error in mounted hook: "TypeError: Cannot read property 'role' of undefined"

found in

---> <Header> at src/components/Header.vue
       <App> at src/App.vue
         <Root>

I tried to make this request with an async/await, I tried methods mounted(), created(), beforeMount(), beforeCreate() but still it's the same. I'm new to Vue.js, and I am stuck here and don't know what to do.

Edit with the whole files to see the app structure: main.js

import router from './router'
import Vue from 'vue'
import App from './App.vue'
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import axios from 'axios'
import Vuex from 'vuex'

// Install BootstrapVue
import 'leaflet/dist/leaflet.css';

Vue.use(BootstrapVue)
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin)
Vue.use(axios)
Vue.use(Vuex)
Vue.config.productionTip = false

const store = new Vuex.Store({
  state: {
    user : {}
  },
  mutations : {
    set_user (state,user) {
      state.user = user
    }
  }
})

export default store

/* eslint-disable */
router.beforeEach((to, from, next) => {
  if (from.path.indexOf("modificarCatalogo") == -1 && to.path.indexOf("modificarCatalogo") == -1) {
    localStorage.removeItem("catalogue");
  }
  if (localStorage.getItem("token") != null) {
    axios
      .get(`${process.env.VUE_APP_API_BASE_URL}/user/role`, {
        headers: {
          Authorization: "Token " + localStorage.getItem("token")
        }
      })
      .then(response => {
        store.commit('set_user', response.data);
        console.log("First then")
        console.log(store.state.user)
      }).catch(function (error) {
         // handle error case here
         console.log(error);

      }).then(function () {
         // always executed
         console.log("Second then")
         next();
      });
     }else{
        next();
     }
});
/* eslint-enable */

Vue.use(router)

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

It has now Vuex because I tried @ellisdod answer but

App.vue

<template>
  <div>
    <Header />
    <router-view />
    <Footer />
  </div>
</template>

And, in Header.vue, it is where I make the call to, in this case, Vuex $store, but it is the same. I need it to be done everywhere, so I tried to call the method in App.vue but still no results, it returns an empty object now with the solution of Vuex, but just empty, not with user data.

export default {
  name: "Header",
  data() {
    return {
      token: localStorage.getItem("token"),
      isOwner: "",
      estancoId: ""
    };
  },
  mounted() {
    console.log("Header log")
    if (this.$store.state.user.role == "Owner") {
      this.isOwner = true;
      this.estancoId = this.$store.state.user.estanco;
    }
  },

The rest of the components are irrelevant I think

Reloxed
  • 23
  • 1
  • 1
  • 7
  • 1
    Use Vuex for storing user info: https://vuex.vuejs.org/ – ellisdod Mar 27 '20 at 13:22
  • Thanks for your help! Does Vuex store the state of the user before the component is loaded? I mean, the problem here I think is that Axios makes the request after the component is loaded, so I think it would the be the same to use Vuex, wouldn't it? – Reloxed Mar 27 '20 at 13:31

2 Answers2

0

If you use Vuex to store your user data you can prefill the user value with an empty object so it won't throw an error.

const store = new Vuex.Store({
  state: {
    user : {}
  },
  mutations : {
    set_user (state,user) {
      state.user = user
    }
  },
  actions : {
    loadUserFromLocal ({commit,state}) {
      if (localStorage.getItem("token") === null) return null
      return axios
        .get(`${process.env.VUE_APP_API_BASE_URL}/user/role`, {
        headers: {
          Authorization: "Token " + localStorage.getItem("token")
        }
      })
      .then(response => {
        commit('set_user', response.data);
        console.log("First then")
        console.log(state.user)
      }).catch(function (error) {
         // handle error case here
         console.log(error);
      })

    }
  }
})

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')

Then in your mounted hook of your main App component add:

mounted () {
    this.$store.dispatch('loadUserFromLocal')
  }

Now in your router rather than making a server request before every route, you just check the store:

if (this.$store.state.user.role) {
  // handle logged in user
}
ellisdod
  • 1,644
  • 10
  • 11
  • Where should I put that "export default new Vuex.Store...." method? I tried it in main.js, the same where my router.beforeEach method is applied, but when I try ```this.$store.commit```, I get an error: ```TypeError: Cannot read property '$store' of undefined``` – Reloxed Mar 27 '20 at 14:18
  • You just need to add it as one of the options when you instantiate vue - updated my answer – ellisdod Mar 27 '20 at 15:36
  • It's not giving the error anymore, however, it is returning the empty object on the first load of the page, because the method was not executed yet, so it is empty. I tried to run it in the App.vue in the created hook, but it is still the same. – Reloxed Mar 27 '20 at 16:01
  • I've updated my answer to move the server request to the store and dispatch it when the app mounts. If you are having issues with the store not loading, this arrangement may help: https://stackoverflow.com/a/45290290/6282604 – ellisdod Mar 27 '20 at 16:41
  • I wish I could select your answer as a solution aswell... I mixed your solution of Vuex, with the one provided by spc, and I worked it out, thank you so much!! – Reloxed Mar 27 '20 at 17:06
-1

Hi,

Complete Answer based on Question Edit,Comments and Answers :

Problem

Vue-router's beforeEach method will only execute in components that are defined in the routes.
In your case,

  1. beforeEach will not be called in Header component as it is not part of routing. It is a standalone component.
    Therefore you cannot access $user inside it.

store.js

    import axios from 'axios'
    import Vuex from 'vuex'

    const store = new Vuex.Store({
    state: {
      user : {}
    },
    mutations : {
      set_user (state,user) {
        state.user = user
      }
    }
    actions : {
        loadUserFromLocal ({commit,state}) {
          if (localStorage.getItem("token") === null) return null
          return axios
            .get(`${process.env.VUE_APP_API_BASE_URL}/user/role`, {
            headers: {
              Authorization: "Token " + localStorage.getItem("token")
            }
          })
          .then(response => {
            commit('set_user', response.data);
            console.log("First then")
            console.log(state.user)
          }).catch(function (error) {
             // handle error case here
             console.log(error);
          })

        }
      }
    })
    export default store

@ellisdod - thanks.

Now you can use the user variable from store in your component and will be updated when the data is done fetched or will show initial values till that time

Therefore no need for fetching data in router.beforeEach

router.js

    // --> all imports
    import store from './store' // --> added
    //  --> all routes to be defined here... 

    router.beforeEach((to, from, next) => {
     // --> other logic for filtering routes
     // --> you can use 'store' variable here to get the user data and add 
    filtering 
    if (from.path.indexOf("modificarCatalogo") == -1 && 
    to.path.indexOf("modificarCatalogo") == -1) {
    localStorage.removeItem("catalogue");
     }
     next();

    });

As you know in vue-router, if next is called then navigation is said to be confirmed and the component will be rendered.
Also for more info on using store variable inside router.beforeEach method refer this question

main.js

    import router from './router'

    import store from './store' // --> added

    import Vue from 'vue'
    import App from './App.vue'
    import { BootstrapVue, IconsPlugin } from 'bootstrap-vue'
    import 'bootstrap/dist/css/bootstrap.css'
    import 'bootstrap-vue/dist/bootstrap-vue.css'
    import axios from 'axios'
    import Vuex from 'vuex'
    // Install BootstrapVue
    import 'leaflet/dist/leaflet.css';

    Vue.use(BootstrapVue)
    // Optionally install the BootstrapVue icon components plugin
    Vue.use(IconsPlugin)
    Vue.use(axios)
    Vue.use(Vuex)
    Vue.config.productionTip = false


    Vue.use(router)

    new Vue({
      router,
      store    // --> added
      render: h => h(App),
    }).$mount('#app')


App.vue

mounted () {
    this.$store.dispatch('loadUserFromLocal')
  }

@ellisdod - thanks.


Header.vue

    export default {
      name: "Header",
      data() {
       return {
        token: localStorage.getItem("token")
       };
      },
      computed: {
       isOwner() {
         return this.$store.state.user.role == "Owner"
       }
       estancoId () {
         return this.$store.state.user.estanco;
       }
      }
      mounted() {
       console.log("Header log")
      },
    }

spc
  • 447
  • 2
  • 6
  • That was a very nice approach I guess, however it didn't solve it, because axios is still being done after the component is loaded, even if we call next inside the then function. I tried to wait for it to be done by putting a ```v-if``` in my router-view call in App.vue, but still not working. Any guess on how to make it wait for axios to be done? – Reloxed Mar 27 '20 at 14:02
  • Can you check by logging to the console and confirm when the three functions are getting executed after axios.get? ie the two 'then' calls and the 'catch' call – spc Mar 27 '20 at 14:16
  • Also if next() isn't called, the component to which you are navigating shouldn't load – spc Mar 27 '20 at 14:21
  • Yes, I just did it to confirm. First, I added a console.log to every call, so the first call is the mounted method on my component, then the first "then" on beforeEach method, and the last one is the second then on beforeEach method. That is because "axios" method is running later than my component mounted method, and that's the problem. – Reloxed Mar 27 '20 at 14:23
  • Also.which component is loaded first in router as the '\home' route? and in which component are you accessing $user – spc Mar 27 '20 at 14:40
  • I edited the answer with the whole files (the needed part to show the problem) so you can, hopefully, understand it better, adding Vuex as @ellisdod suggested – Reloxed Mar 27 '20 at 14:46
  • So, if I did it like that, I should call it from the mounted hook like this.$store.getFetchData() right? – Reloxed Mar 27 '20 at 15:51
  • Also, I want to point out that I don't need it only on Header.vue, that was my bad. I need it everywhere, because I need to execute to check some permissions for users, so that if they enter an URL manually, I don't allow them to enter some of them. However, they are able to do that because this method is not working. I tried to call it in the created hook of App.vue, and it's not working either. – Reloxed Mar 27 '20 at 16:04
  • I finally made it, because, as you told me, Header.vue is not routed, so I did it separately. I mixed your solution with the one provided by ellisdod. Thank you so much for your help!!! – Reloxed Mar 27 '20 at 17:05