Every once in a while in Vue, you want to prevent the user from navigating away from a route or reloading the page. For example, perhaps they made changes to a form without saving.
This behavior is not immediately available, but it’s relatively easy to accomplish.
First, we’ll need some sort of condition to track whether or not the user can navigate away. It may be different in your case, but for this example, we’ll have a boolean tracking whether the user is editing something.
<script>
export default {
data: () => ({
isEditing: false
})
</script>
Prevent URL change and/or page reload.
Next, we need to add some logic to prevent the user from going to a new URL, or reloading the page while our isEditing
is true. Fortunately, the browser has a native beforeunload
event just for this.
We’ll add it to the beforeMount
hook so that we know we are not in a server-rendered environment:
<script>
export default {
// ...
beforeMount() {
window.addEventListener("beforeunload", event => {
if (!this.isEditing) return
event.preventDefault()
// Chrome requires returnValue to be set.
event.returnValue = ""
})
}
}
</script>
Vue does a great job of automatically removing any event handlers that are added to the template, but any that we manually create should be cleaned up to avoid any memory leaks.
To do so, we will refactor our anonymous function into a named method so we can clean it up in the beforeDestroy
hook:
<script>
export default {
// ...
beforeMount() {
window.addEventListener("beforeunload", this.preventNav)
},
beforeDestroy() {
window.removeEventListener("beforeunload", this.preventNav);
},
methods: {
preventNav(event) {
if (!this.isEditing) return
event.preventDefault()
event.returnValue = ""
}
}
}
</script>
If you prefer, you could also put the event listener logic together by using Vue’s $once
method:
<script>
export default {
// ...
beforeMount() {
window.addEventListener("beforeunload", this.preventNav)
this.$once("hook:beforeDestroy", () => {
window.removeEventListener("beforeunload", this.preventNav);
})
},
methods: {
preventNav(event) {
if (!this.isEditing) return
event.preventDefault()
event.returnValue = ""
}
}
}
</script>
Prevent router navigation
Great! So far, our component will prevent a user accidentally losing their changes if the browser changes, but it’s likely that your route changes are actually handled by JavaScript. If that’s the case, you will also need to prevent the Vue router from navigating away.
For this, we can conveniently hook into the in-component navigation guard beforeRouteLeave
(assuming you are using vue-router
).
beforeRouteLeave
, as the name implies, runs whenever you are about to navigate away from the current route. It provides us a few parameters to work with:
to
: the route being navigated to.from
: the route you are about to leave.next
: th function used to invoke navigation. You can also use this to navigate to any other route you like.
For our purposes, we are only interested in the next
parameter, and we can combine this with a confirm
check to ask the user if they want to continue navigation:
<script>
export default {
// ...
beforeRouteLeave(to, from, next) {
if (this.isEditing) {
if (!window.confirm("Leave without saving?")) {
return;
}
}
next();
}
}
</script>
Finishing up
With that, we have a nice little component that prevents a user from navigating away based on our logic. Of course, we did not actually implement any logic, but I will leave that up to you.
The whole thing looks like this:
<script>
export default {
data: () => ({
isEditing: false
}),
beforeMount() {
window.addEventListener("beforeunload", this.preventNav)
this.$once("hook:beforeDestroy", () => {
window.removeEventListener("beforeunload", this.preventNav);
})
},
beforeRouteLeave(to, from, next) {
if (this.isEditing) {
if (!window.confirm("Leave without saving?")) {
return;
}
}
next();
},
methods: {
preventNav(event) {
if (!this.isEditing) return
event.preventDefault()
event.returnValue = ""
},
},
}
</script>
You can see a working example here:
As a next step, this could be a good candidate to create an abstraction in the form of a custom directive, a composable, or as part of a form component. I’ve added it to Vuetensils <VForm>
component, so you can just use that if you want to make your life easier.
If you like this sort of content and would like to get more of it, please sign up for my newsletter and follow me on Twitter.
Thank you so much for reading. If you liked this article, and want to support me, the best ways to do so are to share it, sign up for my newsletter, and follow me on Twitter.
Originally published on austingil.com.