How can I detect the browser back button with Vue? - vue.js

In my component I need to do some logic when the user is navigate to my component using browser "back" button.
Maybe there is a property in vue router or something similar?
How can I detect the browser back button with Vue?

You can use popstate event,
window.addEventListener('popstate', function(event) {
// do something
console.log(event);
});

Related

Vuejs not triggering mounted after user pressed back on his browser

On my vuejs application there is a dashboard, where the user can click a button that send him to /room (router.push("/room");).
When the user arrive on the page, the "mounted" function is triggered and a simple console.log is emited. That works.
mounted() {
console.log("room mounted");
}
If the user press the "back" button of his browser and go back to the dashboard, he can click the button again to join the room, except this time, the "mounted" function is not triggered.
Is there a way to make this works ?
Thank you.
In response to a part of your response to the answer below,
what I'm looking for is when I click again on the button that trigger
the router.push("/room"), because when I'm redirected, mounted nor
updated` are called.
To solve your problem, you can watch the $route object, by doing
watch: {
'$route' () {
// this will be called any time the route changes
}
},
This is expected behavior in Vue Router according to this issue on the Vue Router GitHub repo:
This is expected behaviour, Vue re-uses components where possible.
You can use the beforeRouteUpdatehook to react to a route switch that
uses the same component.
Navigating "back" to an already-mounted component won't trigger a subsequent mounting of the component. To see which lifecycle hooks are triggered on Route Update, you can look at this blog post (scroll down to the Lifecycle Hooks diagram).
The situation you're running into is the "Client Update" column, where mounted is not called, but update is. In general, I tend to utilize parallel code in both beforeRouteEnter and beforeRouteUpdate. Sadly, it's a bit repetitive.

how do I bring up "Are you sure you want to leave page?" popup in vue.js router?

I'm building a vue.js application. We'd like to have a popup come up when the user attempts to leave a specific page. The popup should say "Are you sure you want to leave the page?" I know I can implement something in the beforeRouteLeave hook of the component, but I'm wondering if there's a way to implement this in the beforeEach event of the router (i.e. not the component). The reason I'd like to use the router is because beforeEach in the router seems to respond to the user entering a different path in the browser url bar, whereas the beforeRouteLeave hook on the component does not. However, I don't have access to the popup in the router whereas I do in the component (the popup would just be part of the template).
So the question is: how can I bring up a popup in the router before the user actually leaves the page?
Thanks.
First you can assign a name for each of your routes objects in routes array inside your router or another field like requiredConfirmation or something like that, imagine that we have a routes like this :
routes : [
{
path : '/needconfirm',
component : NeedConfirmToLeaveCom,
name : 'needconfirm-route1'
},
{
path : '/neednotconfirm',
component : NeedNotConfirmToLeaveCom,
name : 'normal-route1'
},
]
then you can use router.beforeEach to set some conditions or some confirmations based on your Origin route and Destination route.
something like this :
router.beforeEach((to,from,next) => {
if(from.name.startsWith("needconfirm-")) {
if(window.confirm("Are you sure you want to leave the page?")) {
next();
}
}else next();
});
UPDATE * :
if you want to use some custom components for your popup, you can use vuex to store your component's logic and toggler and import that component in your App.Vue or other root/child components you wish. because you have access to your store management using $store right?
UPDATE ** :
and one other thing i want to mention, if you want to save some progress or state and because of that you want to get confirmation from user (progress will lost if they switch route), you should consider using Vuex to store your progress or state of your application and if you want more persisted solution you can use VuexPersisted store management which uses LocalStorage.
Vue router navigation guards document
Vuex Doc
You should use beforeunload event listener on the main component in that view.
MDN Reference
Depending on the browser, it will show the popup with default values populated.
This is how I use it in the created hook of the main component
window.addEventListener("beforeunload", (e) => {
e.preventDefault()
// chrome requires returnValue to be set
const message = "You have unsaved changes. Are you sure you wish to leave?"
e.returnValue = message
return message
})

Is it possible for Nuxt middleware to wait for asyncData requests to resolve?

I have a some middleware in my Nuxt app that closes a fullscreen mobile menu when a new route is clicked. What I am experiencing is the following:
user clicks nuxt-link
menu closes
asyncData delay (still shows current page)
new page is loaded upon resolved asyncData
What I would like is the following:
user clicks nuxt-link
asyncData delay (if exists)
menu closes upon new page load
Is it possible to have an asyncData watcher in Nuxt middleware?
I know I can hack this by creating a store value that tracks asyncData load, but I'd rather not have to wire up such a messy solution to each and every page that uses asyncData.
Note - not every page uses asyncData.
I think that the best option would be to leave the menu open, and then when the new page finishes loading (probably on the mounted hook) send an event or action to close the menu.
I figured this out in a more elegant solution than having to implement a store.dispatch function on each individual asyncData function.
So what I did was use a custom loading component: https://nuxtjs.org/api/configuration-loading/#using-a-custom-loading-component
However, instead of showing a progress bar or any sort of loading animation, I just used this component to set a loading state in my vuex store. Note - it forces you to have a template, but I just permanently disabled it.
<template>
<div v-if="false"></div>
</template>
<script>
export default {
methods: {
start() {
this.$store.dispatch('updateLoadingStatus', true);
},
finish() {
this.$store.dispatch('updateLoadingStatus', false);
}
}
};
</script>
Then in my middleware, I set up an interval watcher to check when loading has stopped. When stopped, I stop the interval and close the mobile menu.
export default function({ store }) {
if (store.state.page.mobileNav) {
if (store.state.page.loading) {
var watcher = setInterval(() => {
if (!store.state.page.loading) {
store.dispatch('updateMobileNav', false);
clearInterval(watcher);
}
}, 100);
} else {
store.dispatch('updateMobileNav', false);
}
}
}
This doesn't specifically have to be for a mobile menu open/close. It can be used for anything in middleware that needs to wait for asyncData to resolve. Hope this can help anyone in the future!

Vue router view event propagation

I cant't figure out why the router-view does not emit the "login" event.
Here's the fiddle I'm playing with: https://jsfiddle.net/cvtwxf6h/22/
I want 2 different layouts, one for logged user and another for not logged user. The layout to display is determined by the logged property of the Index component.
When I click "Login" in the login page, a "login" event should propagate up to the Index component to update the logged property and change layout. For some reason the router-view does not emit the event, what am I doing wrong?
(I just want to understand the problem, I'm not interested in alternative ways to achieve this)
The problem seems to be the router-link navigates to a different route (via to="{name: 'index'}") before the login event is emitted, which causes the event to be lost somehow. If the target route is the same as the current route (no navigation), the event reaches the parent component without a problem.
A workaround would be to imperatively navigate with $router.push() after emitting the event:
const LoginPage = {
template: `<router-link to="" #click.native="switchToLoggedPage({ name: 'index' })">Login</router-link>`,
methods: {
switchToLoggedPage(route) {
this.$emit('login');
this.$router.push(route);
},
},
};
demo

Vue Router: does this.$router.push navigate to a new URL?

I read the documentation of vue-router (https://router.vuejs.org/guide/essentials/navigation.html)
This is the method called internally when you click a ,
so clicking is the equivalent of calling
router.push(...)
As far as I know clicking router-link element navigates to the URL placed in "to" attribute. However, according to History API
(https://developer.mozilla.org/en-US/docs/Web/API/History_API#Examples), history.pushState(...) only changes the history and does not navigate to a new URL.
So... how can we explain this contradiction?
I think you need to define exactly what you mean by "navigate to a new URL"; to me it can mean either reloading the page at a new URL, or simply changing the URL in the address bar without reloading the page.
history.pushState() does change the URL, but it doesn't cause the browser to perform a full page reload as is typical when you click a link. This is how "single page apps" work – they intercept <a> clicks and use history.pushState() to prevent the page from reloading.
history.pushState(...) only changes the history and does not navigate to a new URL.
Here I think "and does not navigate to a new URL" is wrong – it does, except the page doesn't reload.
There is no contradiction here. There is no reason why the Vue Router could not do a change to the url with the history api and change the component as rendered in various router-view components.
When you include a router-link in your code, this is a component like any other. Vue will render this component. The interesting part is this:
const router = this.$router
// And later
const handler = e => {
if (guardEvent(e)) {
if (this.replace) {
router.replace(location)
} else {
router.push(location)
}
}
}
const on = { click: guardEvent }
if (Array.isArray(this.event)) {
this.event.forEach(e => { on[e] = handler })
} else {
on[this.event] = handler
}
For the history api, you can see in the source that for a this.$router.push(..) we transition, and we push the state with this pushState function. The transition itself can be found in history/base.js.