Nuxt watch does not redirect - vue.js

I have a Nuxt application with profile page. This page has a watcher which checks store.state.auth.isAuthenticated value. If it is false watcher should redirect to login page. The weird is that although the condition is evaluated right it does not redirect to login.
watch: {
'$store.state.auth.isAuthenticated': {
imediate: true,
deep: false,
handler(newVal) {
if( !newVal ) this.$router.push({'name': 'login'});
}
},
},
As I wrote above, condition is evaluated right but it does not trigger $router.push(). I dont understand it. What is wrong with that code?
EDIT: It creates the endless loop in auth.js middleware.
import { createNavigationGuard } from "~/plugins/navigation-guard.js";
export default function (context) {
if (process.client) {
const watchedStores = [];
const unwatchStore = (unwatch) => {
if (typeof unwatch === "function") {
unwatch();
}
};
// TODO: Find out whether the watchers persist after each route
// Unwatch previous route - this could be necessary for performance
console.log('watchedStores');
console.log(watchedStores);
unwatchStore(watchedStores.pop());
const unwatch = context.store.watch(
(state) => {
return state.auth.isAuthenticated;
},
(isAuthenticated) => {
createNavigationGuard(context, isAuthenticated);
},
{
immediate: true,
}
);
// it's not necessary to reassign unwatched variable to undefined with array
watchedStores.push(unwatch);
}
if (process.server) {
createNavigationGuard(
context,
context.store.state.auth.isAuthenticated
);
}
}

have you tried to make a method for the redirect and just call that method in your watch handler?
so instead of this.$router.push do this.redirectUser()
and in the method 'redirectUser()' do:
this.$router.push({'name': 'login'})

Related

Vue Router Navigation Guards

I have a page that can´t be accessed without permission. The permission is loaded by axios request in an action in the store. After the request the permission is stored in a store module. In the Navigation Guard beforeEach I have a getter that gets the permissions data from the store module.
Because it did not work I wrote a console.log to log the permissions data. The permissions data is an Array and when it logs the length of the Array it logs 0. That doesn´t make sense, because when I see into the Vue DevTools the store says that the array length is 1.
Does anyone have a solution that the store is faster?
Navigation Guard:
router.beforeEach(async (to, from, next) => {
var hasPermission = await store.getters.availableAppPermissions
hasPermission.forEach(function(item) {
if (
to.path.includes(item.appUrl) &&
to.matched.some(record => record.meta.requiresPermission)
) {
next({ name: 'Home' })
}
})
next()
})
Store Module:
import axios from 'axios'
export default {
state: {
availableApps: []
},
mutations: {
SET_AVAILABLE_APPS(state, availableApps) {
state.availableApps = availableApps
state.permissions = true
}
},
actions: {
loadAppsAvailableForCurrentUser({ commit }) {
return axios.get('/v1/apps').then(data => {
// Filter out apps that have false set in show_in_menu
const filteredApps = data.data.filter(app => app.showInMenu)
commit('SET_AVAILABLE_APPS', filteredApps)
})
}
},
getters: {
availableApps(state) {
return state.availableApps
},
availableAppPermissions(state) {
return state.availableApps.filter(item => item.hasPermission == false)
}
}
}
Code where loadAppsAvailableForCurrentUser is called:
This created is in the NavBar Component it is called on every Site because this Component is in the App.vue
created() {
if (this.$store.getters.loggedIn) {
this.$store.dispatch('loadUserData')
this.$store.dispatch('loadUserImageBase64')
this.$store.dispatch('loadVisibleTabs')
this.$store.dispatch('loadAppsAvailableForCurrentUser')
}
}

VueJS router.push not updating data inside page

I am using NuxtJS and I have a NavBar that goes to /website?web=1 and /website?web=2.. When I go from /website?web=1 to /website?web=2 vice versa.. My async fetch is not running at all.
website.vue
async fetch({ store, error, route, params }) {
let parameters;
const pageLimit = store.state.websites.pageLimit;
const offset = params.id ? (params.id - 1) * pageLimit : 0;
const web = route.query.web;
try {
if (web === "1") {
parameters = `?&is_global=true&offset=${offset}&limit=${pageLimit}`;
} else if (web === "2") {
parameters = `?&is_global=false&offset=${offset}&limit=${pageLimit}`;
} else {
parameters = `?co_id=${
route.query.co_id ? route.query.co_id : ""
}&ca_id=${
route.query.ca_id ? route.query.ca_id : ""
}&limit=${pageLimit}&offset=${offset}`;
}
await Promise.all([
store.dispatch("websites/fetchWebsites", parameters)
]);
} catch (e) {
console.log("Error: " + e);
}
},
NavBar.vue
methods: {
handleClick(tab, event) {
switch (tab.label) {
case "1":
this.$router.push({ name: "index" });
break;
case "2":
this.$router.push("/country");
break;
case "3":
this.$router.push("/website?web=1");
break;
case "4":
this.$router.push("/website?web=2");
break;
}
}
}
async fetch lifecycle is not invoked of query / param update
Sometimes you just want to fetch data and pre-render it on the server
without using a store. asyncData is called every time before loading
the page component. It will be called server-side once (on the first
request to the Nuxt app) and client-side when navigating to further
routes doc.
Also, a component does not remount on query / param update, so
lifecycles like created / mounted / beforeCreate etc are also not
invoked again. This helps in the application's performance as it avoids unnecessary rendering of the entire page where a few data changes would work.
Make a common method
methods: {
fetchData ({ store, error, route, params }) {
// your fetch logic here
let parameters;
const pageLimit = store.state.websites.pageLimit;
// ...
}
}
Call the method in async data
async fetch({ store, error, route, params }) {
this.fetchData({ store, error, route, params })
}
Call the method again on query change
watch: {
"$route.query.web": {
handler () {
this.fetchData({ store: this.$store, route: this.$route... });
}
},
Alternative to watch
beforeRouteUpdate (to, from, next) {
if (from.name === to.name) { // Call fn only when: route hasn't changed instead just query / params for the route has changed
this.fetchData({ store: this.$store, route: this.$route... })
}
},
When using Nuxt's fetch(), you need an explicit watcher to listen for route changes.
For a Nuxt component which has async fetch(), if you want it to update when the route changes, then setup a standard watcher.
See docs: https://nuxtjs.org/docs/2.x/features/data-fetching#listening-to-query-string-changes
export default {
watch: {
'$route.query': '$fetch' // This runs $fetch, defined below
},
async fetch() {
// Runs on server load (SSR), or when called (see above)
}
}
For other context's (or before Nuxt 2.12):
You could explore using watchQuery.
See docs: https://nuxtjs.org/docs/2.x/components-glossary/pages-watchquery/
export default {
watchQuery(newQuery, oldQuery) {
// Only execute component methods if the old query string contained `bar`
// and the new query string contains `foo`
return newQuery.foo && oldQuery.bar
}
}
https://nuxtjs.org/docs/2.x/components-glossary/pages-watchquery/

VueJS data doesnt change on URL change

My problem is that when I go from one user page to another user page the info in component still remains from first user. So if I go from /user/username1 to /user/username2 info remains from username1. How can I fix this ? This is my code:
UserProfile.vue
mounted() {
this.$store.dispatch('getUserProfile').then(data => {
if(data.success = true) {
this.username = data.user.username;
this.positive = data.user.positiverep;
this.negative = data.user.negativerep;
this.createdAt = data.user.createdAt;
this.lastLogin = data.user.lastLogin;
data.invites.forEach(element => {
this.invites.push(element);
});
}
});
},
And this is from actions.js file to get user:
const getUserProfile = async ({
commit
}) => {
try {
const response = await API.get('/user/' + router.currentRoute.params.username);
if (response.status === 200 && response.data.user) {
const data = {
success: true,
user: response.data.user,
invites: response.data.invites
}
return data;
} else {
return console.log('Something went wrong.');
}
} catch (error) {
console.log(error);
}
};
Should I add watch maybe instead of mounted to keep track of username change in url ?
You can use watch with the immediate property, you can then remove the code in mounted as the watch handler will be called instead.
watch: {
'$route.params.username': {
handler: function() {
this.$store.dispatch('getUserProfile').then(data => {
if(data.success = true) {
this.username = data.user.username;
this.positive = data.user.positiverep;
this.negative = data.user.negativerep;
this.createdAt = data.user.createdAt;
this.lastLogin = data.user.lastLogin;
data.invites.forEach(element => {
this.invites.push(element);
});
}
});
},
deep: true,
immediate: true,
},
}
Your page is loaded before the data is retrieved it seems, you need put a "loading" property in the data and have a v-if="!loading" for your component then it will only render once the display is updated. Personally I would avoid watch if I can it is not great for performance of for fine grained handling.
Yes you should add wach on statement that contain user info.(you may have a problem to watch on object, so you can save user info in json, but im not sure). When user changing - call action, after recived response call mutation that should change a state, then watch this state.
And you might use better syntax to receive data from store. That is really bad idea call dispatch directly from your mouted hook, use vuex documentation to make your code better.

Vue-router param not updating with back button

I am using a param, and when I push the param using this.$router.push() it works.
routes: {
path: ':stepId?',
name: 'stepper'
}
BUT, I am also watching $route inside a component in order to catch the value of the param changing (As described in the docs):
watch: {
$route: {
handler: function(to, from) {
const newStepId = (to.params && to.params.stepId) || this.steps[0].id;
const initial = !from;
if (initial || newStepId !== from.params.stepId) {
this.goToStep(newStepId, initial);
}
},
immediate: true
}
}
However when I use the back button, either the to section of the route inside the watch: $route doesn't have any param, just the path OR the watch doesn't even run.
I had the same issue, what worked for me was adding the $watch in the created() method.
created() {
this.$watch("$route",() => {
// this.$route.query is watched now as expected
},
{ immediate: true });
}
Still a bit unclear to me though why putting it in mounted or like what you did doesn't work

vuejs2: how can i destroy a watcher?

How can i destroy this watcher? I need it only one time in my child component, when my async data has loaded from the parent component.
export default {
...
watch: {
data: function(){
this.sortBy();
},
},
...
}
gregor ;)
If you construct a watcher dynamically by calling vm.$watch function, it returns a function that may be called at a later point in time to disable (remove) that particular watcher.
Don't put the watcher statically in the component, as in your code, but do something like:
created() {
var unwatch = this.$watch(....)
// now the watcher is watching and you can disable it
// by calling unwatch() somewhere else;
// you can store the unwatch function to a variable in the data
// or whatever suits you best
}
More thorough explanation may be found from here: https://codingexplained.com/coding/front-end/vue-js/adding-removing-watchers-dynamically
Here is an example:
<script>
export default {
data() {
return {
employee: {
teams: []
},
employeeTeamsWatcher: null,
};
},
created() {
this.employeeTeamsWatcher = this.$watch('employee.teams', (newVal, oldVal) => {
this.setActiveTeamTabName();
});
},
methods: {
setActiveTeamTabName() {
if (this.employee.teams.length) {
// once you got your desired condition satisfied then unwatch by calling:
this.employeeTeamsWatcher();
}
},
},
};
</script>
If you are using vue2 using the composition-api plugin or vue3, you can use WatchStopHandle which is returned by watch e.g.:
const x = ref(0);
setInterval(() => {
x.value++;
}, 1000);
const unwatch = watch(
() => x.value,
() => {
console.log(x.value);
x.value++;
// stop watch:
if (x.value > 3) unwatch();
}
);
For this kind of stuff, you can investigate the type declaration of the API, which is very helpful, just hover the mouse on it, and it will show you a hint about what you can do: