I experienced a similar problem with q-layout-drawer and I was seeing the Vue warning: ‘Avoid mutating a property directly…’ .
As you should know, if you are passing the ‘leftDrawerOpen’ variable down to the child component as a prop then you should avoid manipulating that prop directly in the child component. I was not attempting this in my code but I was still seeing the error.
It turns out that q-layout-drawer has a built-in function which detects clicks outside the drawer and attempts to close the itself by updating the v-model ‘leftDrawerOpen’. This works fine when the v-model references data, but when it’s referencing a prop it shows the mutation warning because it’s attempting to manipulate the property.
In order to get around this you have to copy the ‘leftDrawerOpen’ prop variable into a local data variable. This means the q-layout-drawer can manipulate the copied data variable without warnings. However, you have to keep in mind that the ‘leftDrawerOpen’ prop could be changed from the parent or from another component, so to keep our local copy up to date you have to watch for changes to that prop.
Here is an example:
/*CustomDrawerComponent.vue*/
/* Passing local data instead of the prop to q-layout-drawer */
<q-layout-drawer side="left" v-model="localLeftDrawerOpen">
<q-list link inset-delimiter>
<q-item to="/">
<q-item-side icon="home" />
<q-item-main label="Home" sublabel="This is your home" />
</q-item>
</q-list>
</q-layout-drawer>
</template>
<script>
export default {
props: {
/* this prop gets passed down from our parent - it should NOT be manipulated directly! */
leftDrawerOpen: Boolean
},
data () {
return {
/* A local data copy of our prop - which CAN be manipulated here */
localLeftDrawerOpen: false
}
},
watch: {
/* If our prop ever gets changed outside of this component then we need to update our local data version of the prop */
leftDrawerOpen: function(newVal) {
this.localLeftDrawerOpen = newVal;
}
},
mounted: function() {
/* As soon as the component is mounted convert our passed prop into data*/
/* This line may or may not be necessary - The watch function probably covers it already, but I haven't tested without it yet */
this.localLeftDrawerOpen = this.leftDrawerOpen;
}
}
</script>
/* Layout */
<template>
<q-layout>
<q-layout-header>
<q-toolbar color="primary" :inverted="$q.theme === 'ios'">
<q-btn flat dense round @click="leftDrawerOpen = !leftDrawerOpen" aria-label="Menu">
<q-icon name="menu" />
</q-btn>
<q-toolbar-title>
My Custom App
<div slot="subtitle">Running on Quasar v{{ $q.version }}</div>
</q-toolbar-title>
</q-toolbar>
</q-layout-header>
/*passing our parent's data down to our child as a prop*/
<custom-drawer :left-drawer-open="leftDrawerOpen"></custom-drawer>
<!-- this is where the Pages are injected -->
<q-page-container>
<router-view></router-view>
</q-page-container>
</q-layout>
</template>
<script>
//Our custom components
import CustomDrawer from 'components/CustomDrawer'
export default {
components: {
CustomDrawer
},
// name: 'LayoutName',
data () {
return {
leftDrawerOpen: this.$q.platform.is.desktop
}
},
mounted () {
var self = this;
}
}
</script>
And to go a little further, if you want to keep your header in a separate component then do something like this using Quasar’s global event bus:
/*CustomHeader.vue*/
/*Emit a request to update the leftDrawerOpen variable*/
<q-btn flat dense round @click="$root.$emit('toggle_left_drawer')" aria-label="Menu">
<q-icon name="menu" />
</q-btn>
/*Layout.vue*/
mounted () {
var self = this;
//List for $emits from any child components
this.$root.$on('toggle_left_drawer', function() {
/* our parent's data is updated, which in turn updates the sidebars props, which in turn updates the sidebars local data - NO WARNINGS :) */
self.leftDrawerOpen = !self.leftDrawerOpen;
});
}
``