Vue Router code firing before app creation finished? - vue.js

Whenever I try to load / refresh my app via a deeplink the router code fires first. And fails because the authetication token has not been set yet... I assume that the beforeCreate of the app should be the first thing to be executed.
The browser console displays:
router beforeEnter
app beforeCreate
Router code:
...
const router = new VueRouter({
routes: [{
path: '/article/:articleId',
name: 'article',
component: Article,
beforeEnter (to, from, next) {
console.log('router beforeEnter')
// Load stuff from backend with axios
}
}]
}
Application startup code
...
Vue.use(VueRouter)
import router from './router'
new Vue({
el: '#app',
store: store,
router: router,
beforeCreate: function() {
console.log('app beforeCreate')
// get authentication token from localStorage and put in axios header
},
render: h => h(App),
})
What am I missing here? How can I make sure the app creation code is executed first?

I think the behaviour is intended and correct.
Before something get's rendered the router decides what to render.
But how to solve your problem?
First i have a persistent auth module like this:
export default {
name: 'auth',
namespaced: false,
state: {
token: undefined,
payload: undefined
},
mutations: {
clearAuth (state) {
state.token = undefined
state.payload = undefined
},
setToken (state, token) {
let payload
try {
payload = JSON.parse(atob(token.split('.')[1]))
} catch (e) {
payload = undefined
token = undefined
}
state.token = token
state.payload = payload
}
},
getters: {
token: state => state.token,
isAuthenticated: state => !!state.token,
hasRenewToken: state => !!state.payload && state.payload.renewable
}
}
Then i use vuex-persistedstate to initialise the vuex module.
import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
modules: {
auth,
... // other modules
},
plugins: [
createPersistedState({
paths: ['auth']
})
]
})
Now whenever the store is created all auth informations are in the store.
And at the end i use a little wrapper for axios like this (request.js):
import axios from 'axios'
import store from '#/store'
const requestHandler = config => {
config.headers = {
'Authorization': store.getters.token
}
config.crossDomain = true
config.method = 'POST'
return config
}
const request = axios.create()
request.interceptors.request.use(requestHandler)
export default request
Now i do not import axios but request.js whereever i want to make a request.
I hope this approach helps you. At least it works for me

Have you tried loading it before the router? AFAIK the Vue object loads everything synchronous.
new Vue({
el: '#app',
store: store,
beforeCreate: function() {
console.log('app beforeCreate')
// set authentication token in axios header
},
router: router,
render: h => h(App),
})

Related

vuex unknown action type: 'auth/Signup'

I was trying to create a signup page using my auth module in vuex. I posted an api for signing up from action in the module. When I tried this code, it said "[vuex] unknown action type: auth/signUp" in the console. Did I do anything wrong? Can anyone solve this?
This is my vuex auth module
// store/auth/index.jx
import auth from '#/API/API_Auth'
const state = () => ({})
const getters=()=>({})
const mutations = () => ({})
const actions = () => ({
signUp({commit},data){
return auth.signUp(data)
.then(res=>{
console.log(res)
})
.catch(err=>{
console.log(err)
})
}
})
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
My vuex store.
// store/index.js
import Vue from "vue";
import Vuex from "vuex"
import auth from './module/auth'
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
auth,
},
state:{},
getters:{},
mutations:{},
actions:{},
})
I imported the store and router in main.js
// main.js
import store from "./store"
import router from "./router";
new Vue({
store,
router,
render: (h) => h(App),
}).$mount("#app");
This is my sign up component where I call the action.
// src/component/signup.vue
<script>
export default {
data() {
return {
name: "",
telNumber: "",
};
},
methods: {
handleSubmit() {
let name= this.name
let telNumber= this.telNumber
this.$store.dispatch("auth/signUp", {name,telNumber})
.then(res=>{
this.$router.push({path: 'otp'});
})
.catch(err=>{console.log(err)})
}
}
}
};
</script>
Your Vuex module incorrectly sets actions, mutations, and getters as functions. Only state should be a function, and the rest should be objects:
const state = () => ({}) // ✅ function
const getters = {} // ✅ object
const mutations = {} // ✅ object
const actions = { // ✅ object
signUp({ commit }, data) {}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}

TypeError: Cannot read property 'token' of undefined store dispatch vuex

I have a vue component that makes use of the store Vuex. However I get a
TypeError: Cannot read property 'token' of undefined
error. I don't understand why. This is my code:
In main.js:
import Vue from 'vue'
import Vuex from 'vuex';
import App from './App.vue'
import router from './router';
import "./assets/css/tailwind.css";
import '#/assets/css/tailwind.css';
import store from './store';
Vue.config.productionTip = false;
Vue.use(Vuex);
new Vue({
router, store,
render: h => h(App),
}).$mount('#app');
In store/indes.js:
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
},
state: {
token: ''
}
})
In GenericForm.vue:
methods: {
execute() {
console.log("GenericForm.vue")
if (this.inputFunction) {
this.inputFunction()
}
this.register()
},
register () {
console.log("register()")
try {
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
})
this.$store.dispatch('setToken', response.data.token)
this.$store.dispatch('setUser', response.data.user)
this.$store.router.push({
name: 'songs'
})
} catch (error) {
console.log(error)
/*
this.error = error.response.data.error
*/
}
}
}
the error occurs on this line of code:
this.$store.dispatch
Any help is appreciated.
EDIT:
AuthenticationService.js
import api from './api'
export default {
register (credentials) {
return api().post('register', credentials)
}
}
api.js
import axios from 'axios'
export default() => {
return axios.create({
baseURL: 'http://localhost:8081'
})
};
After adding console.log:
EDIT2:
New method:
register: async () => {
console.log("register()")
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
}).then((response) => {
console.log(response)
/* this.$store.dispatch('setToken', response.data.token)
this.$store.dispatch('setUser', response.data.user)*/
this.$store.router.push({
name: '/test'
})
});
}
I get the error on
this.$store.router.push({
name: '/test'
})
line:
The response gets logged alright, though.
There are two problems:
First problem:
This code:
register(credentials) {
return api().post('register', credentials)
}
is returning a Promise, which has no data property. What you want is to access the axios response wrapped in that promise, so you either:
call then on the promise
AuthenticationService.register({...}).then((response) => {
console.log(response.data.token) // 'foo'
});
use async/await inside the Vue component
Second problem
The problem that causes the store to be undefined, is the use of the arrow functions. The register() method shouldn't have an arrow. Once the arrow gets removed there is no error (store is defined, as well as a router):
async register() {
console.log("register()")
const response = AuthenticationService.register({
email: 'testss',
password: 'frgr'
}).then((response) => {
console.log(response)
console.log(this.$store)
this.$router.push({
name: 'ha'
})
});
}
This means that the data property of response is not defined.
Is the AuthenticationService.register method asynchronous?
I'd imagine it is. If so, your code is continuing before the response object has been properly resolved.
Take a second and run console.log(response). You may see an unresolved promise if the method is async.
Otherwise, you may see nothing defined at all if the method does not return anything but instead uses callbacks.

Unknow action type error using vuex store

I am trying to dispatch an action but I get this error: "Unknow action type".
I got why i have this error but I just don't know what i did wrong.
My Component:
created() {
this.$store.dispatch('techValid/fetchTechValids');
},
My Store (index.js) :
import Vue from 'vue';
import Vuex from 'vuex';
import techValid from './modules/techValid';
Vue.use(Vuex);
export default new Vuex.Store({
modules: { techValid,
},
});
My store (techValid.js is ina module folder in the store):
actions: {
async fetchTechValids() {
await axios
.get('http://localhost:3080/techValid')
.then((response) => {
console.log('API CALL OK');
console.log(response);
techValid.commit('SET_ALL_TECHVALIDS', response);
})
.catch((error) => {
console.log('API CALL NOT OK', error);
throw new Error(`${error}`);
});
},
},
Main.js:
//Some imports
new Vue({
el: '#app',
store,
router,
components: { App },
template: '<App/>',
});
You can simply call the actions with the action name like this
created() {
this.$store.dispatch('fetchTechValids');
}
You don't need to specify the module name while calling actions and mutations.
And inside the action function, you can call the mutations like
actions: {
async fetchTechValids({commit}) {
let response = await axios.get('http://localhost:3080/techValid'); // since async function is using, you can directly get the response.
console.log('API CALL OK');
console.log(response);
commit('SET_ALL_TECHVALIDS', response);
},
}
You can use if condition to before calling the commit and can use the try-catch to catch the errors.
Ok thanks to all of you!
It works now, I removed the module name in my dispatch : this.$store.dispatch('fectTechValids')
And removed the namespaced in my store and just export state, getters, mutations, actions

Vue-router: Using component method within the router

My first Vue project and I want to run a loading effect on every router call.
I made a Loading component:
<template>
<b-loading :is-full-page="isFullPage" :active.sync="isLoading" :can-cancel="true"></b-loading>
</template>
<script>
export default {
data() {
return {
isLoading: false,
isFullPage: true
}
},
methods: {
openLoading() {
this.isLoading = true
setTimeout(() => {
this.isLoading = false
}, 10 * 1000)
}
}
}
</script>
And I wanted to place inside the router like this:
router.beforeEach((to, from, next) => {
if (to.name) {
Loading.openLoading()
}
next()
}
But I got this error:
TypeError: "_components_includes_Loading__WEBPACK_IMPORTED_MODULE_9__.default.openLoading is not a function"
What should I do?
Vuex is a good point. But for simplicity you can watch $route in your component, and show your loader when the $route changed, like this:
...
watch: {
'$route'() {
this.openLoading()
},
},
...
I think it's fast and short solution.
I don't think you can access a component method inside a navigation guard (beforeEach) i would suggest using Vuex which is a vue plugin for data management and then making isLoading a global variable so before each route navigation you would do the same ... here is how you can do it :
Of course you need to install Vuex first with npm i vuex ... after that :
on your main file where you are initializing your Vue instance :
import Vue from 'vue'
import Vuex from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
isLoading: false,
},
mutations: {
openLoading(state) {
state.isLoading = true
setTimeout(() => {
state.isLoading = false
}, 10000)
},
},
})
// if your router is on a separated file just export the store and import it there
const router = new VueRouter({
routes: [
{
// ...
},
],
})
router.beforeEach((to, from, next) => {
if (to.name) {
store.commit('openLoading')
}
next()
})
new Vue({
/// ....
router,
store,
})
In your component:
<b-loading :is-full-page="isFullPage" :active.sync="$store.state.isLoading" :can-cancel="true"></b-loading>

Vue Router: Keep query parameter and use same view for children

I'm rewriting an existing Angular 1 application with Vue.
The application always needs to authenticate an user by locale, id and token before entering any views. Respecting the conventions of our API, I specified the token as a query parameter within my main parent route.
Coming from the existing Angular's UI router implementation I thought this is the way to go:
// main.js
new Vue({
el: '#app',
router,
store,
template: '<router-view name="main"></router-view>'
})
// router.js
const router = new Router({
mode: 'history',
routes: [
{
name: 'start',
path : '/:locale/:id', // /:locale/:id?token didn't work
query: {
token: null
},
beforeEnter (to, from, next) {
// 1. Get data from API via locale, id and token
// 2. Update store with user data
},
components: {
main: startComponent
},
children: [{
name: 'profile',
path: 'profile',
components: {
main: profileComponent
}
}]
}
]
})
When I navigate to the profile view, I expect the view to change and the query token to stay, e.g. /en-US/123?token=abc to /en-US/123/profile?token=abc. Neither happens.
I'm using Vue 2.3.3 and Vue Router 2.3.1.
Questions:
Can I keep query parameters when navigating to child routes?
Am I using the Vue router right here? Or do I need to blame my UI router bias?
You can resolve this in the global hooks of Router
import VueRouter from 'vue-router';
import routes from './routes';
const Router = new VueRouter({
mode: 'history',
routes
});
function hasQueryParams(route) {
return !!Object.keys(route.query).length
}
Router.beforeEach((to, from, next) => {
if(!hasQueryParams(to) && hasQueryParams(from)){
next({name: to.name, query: from.query});
} else {
next()
}
})
If the new route (to) does not have its own parameters, then they will be taken from the previous route (from)
You can add in a mounted hook a router navigation guard beforeEach like this preserveQueryParams:
// helpers.js
import isEmpty from 'lodash/isEmpty';
const preserveQueryParams = (to, from, next) => {
const usePreviousQueryParams = isEmpty(to.query) && !isEmpty(from.query);
if (usePreviousQueryParams) {
next({ ...to, query: from.query });
} else {
next();
}
};
// StartComponent.vue
removeBeforeEachRouteGuard: Function;
mounted() {
this.removeBeforeEachRouteGuard = this.$router.beforeEach(preserveQueryParams);
}
// don't forget to remove created guard
destroyed() {
this.removeBeforeEachRouteGuard();
// resetting query can be useful too
this.$router.push({ query: undefined });
}