SmellydogCoding
SmellydogCoding

Reputation: 464

Vuetify Navigation Drawer works once then stops

I'm using a Vuetify navigation drawer. The element that opens it is in a separate component (v-toobar-side-icon) in the header. I'm storing the open/close state of the drawer in the store. I'm using a computed method to get the state of the drawer. I keep getting the error message

Computed property "drawerState" was assigned to but it has no setter.

I know that this is happening because I have to use v-model to control the visibility of the drawer. I don't want to use a computed setter to change the state of the drawer, I want to use a click method.

I tried using :value instead of v-model but I can't get that to work correctly either. The actual problem I am having is that the nav drawer controls (open/close) work on the home page, but once I navigate to another page they stop working. If I navigate back to the home page they still don't work. The getters and setters are changing the state and updating the way they're supposed to, but the isActive property of the nav drawer stays false.

App.vue

<template lang="pug">
  v-app
    app-NavDrawer
    app-header
    router-view
    v-footer(app dark)
      span &copy; 2018 #[a(href="http://www.smellydogcoding.com") Smellydog Coding]
</template>

<script>
  import Header from './components/header/Header.vue'
  import NavDrawer from './components/header/NavDrawer.vue'
  export default {
    data () {
      return {

      }
    },
    components: {
      appHeader: Header,
      appNavDrawer: NavDrawer
    }
  }
</script>

<style>
html {
  overflow-y: auto;
}
a {
  text-decoration: none;
}
 footer {
   color: white;
 }
</style>

Header.vue

<template lang="pug">
  v-toolbar.mt-0(dark)
    v-toolbar-side-icon(@click.stop="openDrawer")
    router-link(to="/" tag="v-toolbar-title") Pool Math
</template>

<script>

export default {
  methods: {
    openDrawer() {
      this.$store.dispatch('navDrawer','open');
    }
  }
}
</script>

<style scoped>
  .toolbar__title {
    cursor: pointer;
  }
</style>

NavDrawer.vue

<template lang="pug">
  v-navigation-drawer(v-model="drawerState" dark fixed temporary)
    v-list.pa-1
      v-list-tile(avatar tag="div")
        v-list-tile-avatar
          img(src="../../../public/v.png")
        v-list-tile-content
          v-list-tile-title Guest
        v-list-tile-action
          v-btn(icon @click.stop="closeDrawer")
            v-icon close
    v-list.pt-0(dense)
      v-divider(light)
      router-link(to="/s1p0" tag="v-list-tile" active-class="active").expansion-panel__header.how-to
        v-list-tile-content
          v-list-tile-title.text-xs-center How to Use This Website
      v-expansion-panel
        v-expansion-panel-content
          div(slot="header") Section 1 - Conversions
          router-link(to="/s1p0" tag="v-list-tile" active-class="active")
            v-list-tile-content
              v-list-tile-title 1.0 - Useful Conversions
          router-link(to="/s1p1" tag="v-list-tile" active-class="active")
            v-list-tile-content
              v-list-tile-title 1.1 - Convert ounces to pounds
          router-link(to="/s1p2" tag="v-list-tile" active-class="active")
            v-list-tile-content
              v-list-tile-title 1.2 - Convert fluid ounces to gallons
          router-link(to="/s1p3" tag="v-list-tile" active-class="active")
            v-list-tile-content
              v-list-tile-title 1.3 - Convert fluid ounces to cups
          router-link(to="/s1p4" tag="v-list-tile" active-class="active")
            v-list-tile-content
              v-list-tile-title 1.4 - Convert inches to feet
        v-expansion-panel-content
          div(slot="header") Section 2 - Area and Volume
          router-link(to="/s2p0" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.0 - Introduction to This Section
          router-link(to="/s2p1" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.1 - Area of a Swimming Pool
          router-link(to="/s2p2" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.2 - Area of a Hot Tub
          router-link(to="/s2p3" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.3 - Volume of Water in a Swimming Pool
          router-link(to="/s2p4" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.4 - Volume of Water in a Multi-Depth Pool
          router-link(to="/s2p5" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.5 - Volume of Water in a Hot Tub
          router-link(to="/s2p6" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.6 - Volume of Water in a Hot Tub with Seats
          router-link(to="/s2p7" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.7 - Volume of Water Loss in a Swimming Pool
          router-link(to="/s2p8" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 2.8 - Volume of Water Loss in a Hot Tub
        v-expansion-panel-content
          div(slot="header") Section 3 - Water Balance
          router-link(to="/s3p0" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 3.0 - Introduction to This Section
          router-link(to="/s3p1" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 3.1 - Calculate Saturation Index
          router-link(to="/s3p2" tag="v-list-tile" active-class="active")
            v-list-tile-content
                v-list-tile-title 3.2 - Calculate Saturation Index - With CA
</template>

<script>
export default {
  computed: {
    drawerState() {
      return this.$store.getters.navDrawer; 
    }
  },
  methods: {
    closeDrawer() { this.$store.dispatch('navDrawer','close')}
  }
}
</script>

<style scoped>
  aside {
    overflow-y: auto;
  }
  .navigation-drawer {
    padding: 0 0 1.0rem;
  }
  .how-to {
    border-bottom: 1px solid rgba(255,255,255,0.12);
  }
  .how-to .list__tile__title {
    font-size: 1.15rem;
  }
</style>

store.js

import Vue from 'vue';
import Vuex from 'vuex';
import axios from 'axios';

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    drawer: false
  },
  getters: {  // send state to a component
    navDrawer: state => {
      return state.drawer
    }
  },
  mutations: { // modify state synchronously
    navDrawer: (state, command) => {
      command === 'open' ? state.drawer = true : state.drawer = false;
    }
  },
  actions: { // modify state aschronously
    navDrawer: ({commit}, command) => {
      commit('navDrawer', command)
    }
  }
});

Upvotes: 2

Views: 8374

Answers (3)

Matheus Dal&#39;Pizzol
Matheus Dal&#39;Pizzol

Reputation: 6105

If anyone is looking for a way to get Vuetify's Navigation Drawer working with a Vuex state property, here's how I managed the case: the secret to avoid an infinite loop that crashes the app is to have a conditional on the setter method of the computed property which ensures that the $store.state property will only be changed if the component's v-model value is different from the one in the $store.state.

  computed: {
    navigation: {
      get () {
        return this.$store.state.navigation
      },
      set (state) {
        if (state !== this.$store.state.navigation) {
          this.$store.dispatch('toggleNavigation')
        }
      }
    }
  },

Upvotes: 3

SmellydogCoding
SmellydogCoding

Reputation: 464

I was able to solve this (though technically it was a work around) by moving the hamburger icon v-toolbar-side-icon from the header component to the nav drawer component:

v-toolbar-side-icon(@click="drawer = !drawer") v-navigation-drawer(v-model="drawer" dark fixed temporary width="350")

since the open and close buttons are now in the same component I can use

@click="drawer = !drawer"

to toggle the nav drawer visibility.

Thomas Ferro:

I did try out your solution and it does work well with one minor modification:

methods: {
    updateDrawerState(state) {
      if (!state) { this.closeDrawer(); }
      else { this.openDrawer(); }
      console.log(state)
    },
    openDrawer() { this.$store.dispatch('navDrawer','open') },
    closeDrawer() { this.$store.dispatch('navDrawer','close') }
  }

When I tried just using

updateDrawerState(state) {
  if (state) {
    this.closeDrawer();
  }
}

what happens is that when I click the hamburger the nav drawer would start to open and then immediately close. I think that this happened because as soon as you click the hamburger to open it that changes the state and every state change closed the drawer. By checking the state and then firing openDrawer or closeDrawer depending on state, everything works as expected.

Thank you for suggesting :value and more to the point, for pointing out that I need to listen to @input (other posts that I found about :value do not mention this.) Thank you very much for that!

Upvotes: 1

Thomas Ferro
Thomas Ferro

Reputation: 2442

If you don't want to use the v-model, you can replace it with a binding of the value via the :value attribute, as you said. But if you do so, you'll have to listen to the @input event, raised when the value change.

In my opinion, you'll be able to achieve what you're trying to do by replacing the v-model for a :value="drawerState" and by binding a new method updateDrawerState to the @input event.

Your NavDrawer.vue will start with

<template lang="pug">
  v-navigation-drawer(:value="drawerState" @input="updateDrawerState" dark fixed temporary)

And you'll have to add this method in the same component :

updateDrawerState(state) {
  if (state) {
    this.closeDrawer();
  }
}

FYI : You can achieve pretty much the same thing by adding a setter to your computed :

computed: {
    drawerState: {
      get() {
        return this.$store.getters.navDrawer; 
      },
      set(state) {
        if (state) {
          this.closeDrawer();
        }
      },
    },
},

Upvotes: 6

Related Questions