[Vuejs]-Closing a Modal/Menu with the back button in Vue/Nuxt

1👍

For this case, my code is similar with Cosimo’s Answer but with different approach.

the difference is i keep the modal triggered by the data property ( this.isMenuOpen )

data(){
  return {
    isMenuOpen: false
  }
}

So you can use it to bind in modal component

<modal v-model="isMenuOpen" />

and i add a watch property to watch the query

watch: {
  $route(newVal, oldVal) {
    this.isMenuOpen = newVal.query?.isMenuOpen || false
  }
}

then in your openMenu methods you can do this

openMenu(){
  this.isMenuOpen = true
  this.$router.push({
    name : "same-path",
    query : {
      isMenuOpen : true
    }
  })
}

But, in your closeMenu use the $router.replace instead of $router.push

closeMenu(){
  this.isMenuOpen = false
  this.$router.replace({
    name : "same-path"
  })
}

Why I use the $router.push for openModal only ? that because it save the history of state, so whenever it open the modal, it will tell the browser new state and than you still can use the back button to the previous state.
After pressed the back button it will remove the query param and it will trigger the watch property

And why use the $router.replace in closeModal ? because it will just replace the state, So when you press back button it will go back to the previous url page instead of open the modal

1👍

hypothesis, you could bind the state of the modal (open / closed) with a property of the router, perhaps your-route?isMenuOpen=[true/false]

and on the click of the [open modal] instead of doing

this.isMenuOpen = true 

do:

this.$router.push({path:'same-path', query: {isMenuOpen: true}});

and the modal is binded with

<modal v-model="$router.query.isMenuOpen" />

1👍

I believe that one of the RFCs merged for vue-router for Vue 3 includes better handling for modal dialogs, but there’s alternatives that work fine now.

there must be something to prevent the default behavior of the back button

That is done by pushing a new route—you can do that with a query parameter as described in the other answer, or as a new route. I chose to use a new route, and it works quite well.

Install @nuxtjs/router-extras and make sure to add it to your buildModules as described in the installation instructions. Then, if your page is at pages/a.vue, create the file pages/a/some-modal.vue (with some-modal being the path name): this creates a child route. Here, write the contents of your modal dialog. Include the following in that component:

<router>
meta:
  showModal: true
</router>

Now, add a watcher in the parent page (the one that will host the modal dialog) for $route.meta. Property decorator + TypeScript syntax:

export default class Index extends Vue {
    showModal = false;

    @Watch('$route.meta', { immediate: true })
    navigate(meta: { showModal?: boolean }) {
        this.showModal = !!meta.showModal;
    }

    back() {
        this.showModal = false;
        if (window.history.length > 2) {
            this.$router.back();
        } else {
            const pathPaths = this.$route.path.split('/');
            pathPaths.pop();
            this.$router.push(pathPaths.join('/'));
        }
    }
}

In your template, include:

<modal-component v-model="showModal" @close="back">
    <NuxtChild :extra-props="extraProps" />
</modal-component>

To open the modal dialog:

<nuxt-link to="some-modal" append>Open</nuxt-link>

This doesn’t preserve query parameters. It shouldn’t be too hard though to create custom logic to do that, however.

1👍

Short answer: don’t make a modal appear like a page in the first place

Longer answer: excellent question, I had the same problem, tried to run through the answers, which were very good hacks. But with every hack comes a caveat, the caveat in this case was that the history state still retained all those pushes. So while you could open a modal (router push) and close it once with the back button (using router replace), when you opened a modal and closed it multiple times, you will quickly find out that you are having to press the back button many more times now to get to the state you are after, this is because the history state now has many more states. I tried to find a way to ‘delete’ a state but its not possible from my findings (even $router.go(-1) had caveats)

None of this is a problem if your modal is clearly visible as a modal (transparent background surrounding it), which was the case for me in tablet / desktop view. On mobile I tried to be cheeky and set a background color matching that of the modal, and it appeared as a nice flush new page, even though it was just a modal (with the close ‘x’ icon top right as usual).

Best solution I could come up with is to watch the modal state and if open add a beforeunload function that will prompt the user if they are sure they want to leave.

'modal.isOpen': {
    immediate: true,
    handler: function(newVal) {
        if (newVal) {
            window.onbeforeunload = this.beforeWindowUnload;
        }
        else {
            window.onbeforeunload = null;
        }
    }
}
👤Leon

Leave a comment