Vue.prototype is undefined on first load of the page

Viewed 9307

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

2 Answers

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
}

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")
      },
    }

Related