Search code examples
vue.jsvuejs2vue-componentvuexvuetify.js

v-navigation-drawer drops into a runaway loop on window resize


First, let me say that the v-navigation-drawer works as intended, i.e.:

  • On clicking the hamburger menu the TOGGLE_DRAWER mutation is committed, and it toggles open/closed, updating the state.
  • On window resize it opens/closes at a designated breakpoint

So it works.

BUT the window resize does not properly toggle the mutation and I keep getting a Vuex mutation error when I resize the window:

enter image description here

I understand why I'm getting this error - the $store.state.ui.drawer is being modified outside of the mutator (it's the v-navigation-drawer's v-model):

<v-navigation-drawer
        v-model="$store.state.ui.drawer"
        app
        clipped
    >

I get it's bad form to bind the state to the v-model. But when I try to make a drawer computed property with a get() and set() method that properly gets/commits a mutation, the browser crashes (presumably because the set method triggers an endless loop of commits toggling drawer true/false into infinity):

computed: { 
            drawer: {
                get () {
                    return this.$store.state.ui.drawer
                },
                set () {
                    this.$store.commit('TOGGLE_DRAWER') // <--crashes the browser
                }
            }
        }

I've searched endlessly for a solution to this problem. It's bugging me even though it visually appears to be working.

I've considered running the v-navigation-drawer in stateless mode and handling all the window resize events and state updates manually. I've also considered disabling 'Strict' mode in Vuex (which would hide the errors). But the former is a lot more complexity and the latter is a bandaid that costs me debugging insight in development.


Solution

  • After spending some time with this, I think I have a solution. Wanted to share for anyone else that may be facing the same issue with VNavigationDrawer using Vuex state to control visibility.

    The @input event passes a val parameter, which includes the state of the drawer after the window resizes. I created a new action that is called by the below function:

    <v-navigation-drawer
        :value="$store.state.ui.drawer"
        app
        clipped
        @input="updateDrawer($event)"
    >
    

    Here is the action being dispatched:

    methods: {
        updateDrawer(event) {
            if (event !== this.drawer) { // avoids dispatching duplicate actions; checks for unique window resize event
                this.$store.dispatch('updateDrawer',event)
            }
        }
    },
    

    And the action commits the new val to the Vuex store.

    Basically, the input event is able to watch for updates to the drawer, and subsequently update the drawer state if it's necessary.

    You'll also see above that I stubbornly accepted using :value as the docs suggest, even though I think this should be controlled by a v-model.

    Seems to be working - with the right events called and the state being updated appropriately.