Reputation: 464
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 © 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
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
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
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