Calling methods inside beforeRouteEnter NProgress - vue.js

I am trying to call an API before navigating to the route. The problem is that if I try to call axios call inside beforeRouteEnter it is working fine for example:
{
beforeRouteEnter(routeTo, routeFrom, next) {
NProgress.start()
axios.get('https://jsonplaceholder.typicode.com/posts').then((res) => {
next((vm) => {
vm.data = res.data
})
NProgress.done()
})
},
}
But when I try to call an API from methods it's navigating to the route before resolving an API and also NProgress bar is also completing before resolving a call.
{
beforeRouteEnter(routeTo, routeFrom, next) {
NProgress.start()
next((vm) => {
vm.index()
NProgress.done()
})
},
methods: {
index() {
axios
.get('https://jsonplaceholder.typicode.com/posts')
.then((res) => {
console.log(res.data)
})
.catch((error) => {
console.log(error)
})
},
},
}
Can anyone guide me what may be wrong?

In your first example, you set the progress bar, then you call the API with Axios and with .then you chain a function after the call. This means the function will wait until the promise is resolved or rejected, before continuing. Only when the axios call is finished successfully, the next function is executed in which you set the data and stop the progress bar. You also should use .catch for if the promises rejects.
Now in your second example, you do not use promises in beforeRouteEnter. Which basically means that all lines are executed immediately. So you call vm.index() and without waiting for the axios call to finish the next line, NProgress.done() will be executed. Although there are several ways to solve this my preference is use async/await, which is just a cleaner way to use promisses and chaining.
In your case I think this would work:
beforeRouteEnter(routeTo, routeFrom, next) {
NProgress.start();
await vm.index();
NProgress.done();
next();
});
}
And the method:
methods: {
async index () {
try {
const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
console.log(response);
} catch (error) {
console.log(error);
}
}
}
Since this is only a part of your component I cannot test it, but I think you get the idea.

Related

Vue3 - Using beforeRouteEnter to prevent flashing content

I am using Vue with axios like
[...]
import VueAxios from "vue-axios";
import axios from "#/axios";
createApp(App)
.use(store)
.use(router)
.use(VueAxios, axios)
.mount("#app");
[...]
which works really great globally like this.axios... everywhere. I am also using Passport for authentification and in my protected route I would like to call my Express-endpoint .../api/is-authenticated to check if the user is logged in or not.
To make this work I would like to use the beforeRouteEnter-navigation guard, but unfortunately I can't call this in there.
Right now I am having in in the mounted-hook, which feels wrong. Is there any solution with keeping my code straight and clean?
I'd appreciate a hint. Thanks.
Edit: This worked for me.
beforeRouteEnter(to, from, next) {
next((vm) => {
var self = vm;
self
.axios({ method: "get", url: "authenticate" })
.then() //nothing needed here to continue?
.catch((error) => {
switch (error.response.status) {
case 401: {
return { name: "Authentification" }; //redirect
//break;
}
default: {
return false; //abort navigation
//break;
}
}
});
});
With beforeRouteEnter there is access to the component instance by passing a callback to next. So instead of this.axios, use the following:
beforeRouteEnter (to, from, next) {
next(vm => {
console.log(vm.axios); // `vm` is the instance
})
}
Here's a pseudo-example with an async request. I prefer async/await syntax but this will make it clearer what's happening:
beforeRouteEnter(to, from, next) {
const url = 'https://jsonplaceholder.typicode.com/posts';
// ✅ Routing has not changed at all yet, still looking at last view
axios.get(url).then(res => {
// ✅ async request complete, still haven't changed views
// Now test the result in some way
if (res.data.length > 10) {
// Passed the test. Carry on with routing
next(vm => {
vm.myData = res.data; // Setting a property before the component loads
})
} else {
// Didn't like the result, redirect
next('/')
}
})
}

ASync/Await is not working as expected in router.BeforeEach guard in vue?

this is my router guard :
router.beforeEach(async (to,from,next)=>{
await store.dispatch('GetPermission');
if(to.matched.some(record => record.meta.requireAuth)){
let permissions=store.state.permissions; //getting empty
console.log(permissions);
if(permissions.filter(per => (per.name === 'read_list').length!=0)){
next({
path:'/dashboard/create'
})
}
else{
next()
}
}
// else if(to.matched.some(record => record.meta.requireAuth)){
// if(store.token!=null){
// next({
// path:'/dashboard'
// })
// }
// else{
// next()
// }
// }
else{
next()
}
});
problem is here though i m using await in dispatch method , i m not getting state value of permissions which is initially empty
here is vuex store code :
GetPermission(context){
axios.defaults.headers.common['Authorization']='Bearer ' + context.state.token
axios.get('http://127.0.0.1:8000/api/user').then((response)=>{
console.log(response)
context.commit('Permissions',response.data.permission)
})
//mutation:
Permissions(state,payload){
state.permissions=payload
}
//state
state:{
error:'',
token:localStorage.getItem('token') || null,
permissions:'',
success:'',
isLoggedin:'',
LoggedUser:{}
}
help me to solve it please ??
actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.
Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously
actions: {
myAction(context, data) {
return new Promise((resolve, reject) => {
// Do something here... lets say, a http call using vue-resource
this.$http("/api/something").then(response => {
// http success, call the mutator and change something in state
resolve(response); // Let the calling function know that http is done. You may send some data back
}, error => {
// http failed, let the calling function know that action did not work out
reject(error);
})
})
}
}
Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:
export default {
mounted: function() {
// This component just got created. Lets fetch some data here using an action
this.$store.dispatch("myAction").then(response => {
console.log("Got some data, now lets show something in this component")
}, error => {
console.error("Got nothing from server. Prompt user to check internet connection and try again")
})
}
}
Also,you are calling same route when no permission match, in that case it always call your same route and make infinite loop.
Redirect to access denied page if permission denied.

vue router next() function don't work in Promise in router.beforeEach

I have the following code:
router.beforeEach((to, from, next) => {
if (to.name !== from.name) {
store
.dispatch("fetchCurrentUser")
.then(() => {
console.log('then');
// do something
next();
})
.catch(() => {
console.log('catch');
router.push("/login");
next();
});
} else {
next();
}
// next();
});
I'm trying to get the current user, and if this succeeds, then do something with this data, and if the request is not successful, then redirect the user to the login page. But next () calls do not work, I get the "then" or "catch" in the console, but the redirect does not occur and an infinite loop begins. But if I take next () from condition (commented row) the redirect works fine.
To redirect you should use next('/') or next({ path: '/' }).
From the documentation:
next: Function: this function must be called to resolve the hook. The
action depends on the arguments provided to next:
next(): move on to the next hook in the pipeline. If no hooks are
left, the navigation is confirmed.
next(false): abort the current navigation. If the browser URL was
changed (either manually by the user or via back button), it will be
reset to that of the from route.
next('/') or next({ path: '/' }): redirect to a different location.
The current navigation will be aborted and a new one will be started.
You can pass any location object to next, which allows you to specify
options like replace: true, name: 'home' and any option used in
router-link's to prop or router.push
The promise resolves after the function ends.
This means that the commented next happens regardless of the result of the promise result. Then the promise resolves and you call another next.
The bottom line is that you don't need the commented next and should just cover the promise resolve.
I was able to implement an async validation inside beforeEach, authentication in my case.
export async function onBeforeEach(to, from, next) {
let { someUserToken } = to.query
if (someUserToken) {
let nextRoute = await authenticate(to)
next(nextRoute)
} else {
const userToken = store.state.application.userToken
if (!to.meta.public && !userToken) {
next({ name: 'Forbidden' })
} else {
next()
}
}
}
async function authenticate(to) {
let { someUserToken, ...rest } = to.query
return store
.dispatch('application/authenticate', {
someUserToken
})
.then(() => {
return {
name: 'Home',
query: {
...rest
}
}
})
.catch(() => {
return { name: 'Forbidden' }
})
}
I hope this helps.

how to pass a reference to a component when calling a vuex action

I'm fairly new to vue (and very new to vuex). I would like to move some axios api calls to be actions in my Vuex store. I know have for example:
actions:{
LOAD_USER: function ({ commit }) {
axios.get('/arc/api/v1/me', {dataType: 'json'})
.then((response )=> {
commit('SET_USER', { user: response.data.user })
})
.catch(function (error) {
console.log(error.message);
});
and call this in my calling component via:
this.$store.dispatch('LOAD_USER')
and this is working. My problem is that I need to set some variables in the calling component to false or kill a progress bar. Here's what I was previously using in my calling component:
this.loading = true
this.$Progress.start()
axios.get('/arc/api/v1/me', {dataType: 'json'})
.then((response )=> {
this.$Progress.finish()
this.loading = false
this.$store.state.user = response.data.user;
this.user = this.$store.state.user
})
.catch(function (error) {
this.$Progress.fail()
console.log(error.message);
});
How would I integrate these loading behaviors into my vuex action? How would I pass a reference to my component via this call:
this.$store.dispatch('LOAD_USER')
or is there a better solution?
Well, you can always use the second parameter of Store.dispatch() to pass any payload into the corresponding action:
this.$store.dispatch('LOAD_USER', this); // passing reference as payload
... but I strongly recommend against doing this. Instead, I'd rather have the whole state (including 'loading' flag, etc.) processed by VueX.
In this case, a single action - LOAD_USER, based on asynchronous API request - would commit two mutations to Store: the first one sets loading flag when the request has been started, the second one resets it back to false - and loads the user data. For example:
LOAD_USER: function ({ commit }) {
commit('LOADING_STARTED'); // sets loading to true
axios.get('/arc/api/v1/me', {dataType: 'json'})
.then(response => {
commit('LOADING_COMPLETE'); // resets loading flag
commit('SET_USER', { user: response.data.user });
})
.catch(error => {
commit('LOADING_ERROR', { error }); // resets loading
console.log(error.message);
});
This approach, among the other advantages, simplifies things a lot when your requests' logic gets more complicated - with error handling, retries etc.
Actions can return a promise https://vuex.vuejs.org/en/actions.html
I think what you want to do is activate the loading when you call your action and stop the loading when the promise is resolved or rejected.
// Action which returns a promise.
actions: {
LOAD_USER ({ commit }) {
return new Promise((resolve, reject) => {
axios.get('/arc/api/v1/me', {dataType: 'json'})
.then((response )=> {
commit('SET_USER', { user: response.data.user })
resolve()
})
.catch(function (error) {
console.log(error.message);
reject(error);
});
})
}
}
// Update loading when the action is resolved.
this.loading = true;
store.dispatch('LOAD_USER').then(() => {
this.loading = false;
})
.catch(function(error) {
// When the promise is rejected
console.log(error);
this.loading = false;
});
If you can't achieve your goal using the above you can add the loading boolean to your vuex store and import it in your component. Than modify the loading boolean inside your action (using mutations) to let the view update.
Note: I would not pass a reference to your actions. While this is possible there are likely better solutions to solve your problem. try to keep the view logic in your components whenever possible.

Execute custom functions one after another - Callback logic in Vue.js

There is a form which submits some data to an API in my component. Assume that it's method is ProcessLogin(). Inside this function I have written my API calls using axios. With the help of then() I have handled my server response and displayed my toast. All good.
Now as a part of my code clean up, I have decided to move all my axios functions to another api.js file and export functions from there. Here is an example function I have in my api.js file :
function ApiLogin(data) {
const url = `${BASE_URL}/authenticate`;
axios.post(url,data).then(response => {
return response;
}).catch(error => {
return error.response;
});
}
On the other side in my component I have my method defined as below :
methods: {
ProcessLogin() {
var status = ApiLogin(this.data);
console.log(status);
}
}
When executing this, I get undefined on my console. I know why it is happening. Because console.log(status) executes before ApiLogin could process and sends it's response. How to handle this kind of situation.? I know that callback is the rescue here, but I am not really sure about how to integrate it.
If you return the axios call from your ApiLogin function:
function ApiLogin(data) {
const url = `${BASE_URL}/authenticate`
return axios.post(url, data)
}
You could then handle the response in your component using then and console log from there:
methods: {
ProcessLogin() {
ApiLogin(this.data)
.then(res => console.log(res))
.catch(err => console.log(err))
}
}
...or with async/await:
methods: {
ProcessLogin: async function() {
try {
var status = await ApiLogin(this.data)
console.log(status)
}
catch(err) {
console.log(err)
}
}
}