How to prevent browser refresh, URL changes, or route navigation in Vue

Every once in a while in Vue, you want to prevent the user from navigating away from a route or reloading the page. This post covers how to do just that.

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.

Leave a Reply

Your email address will not be published. Required fields are marked *