How do I make a state change? - javascript

I made two different components.
in one v-navigation-drawer
the second has a button that changes the States of v-navigation-drawer in vuex.
When on small screens, there is a call to v-navigation-drawer, and after clicking on the gray area, the state in vuex does not change.
enter code here
drawer.vue
<template>
<v-navigation-drawer
fixed
:value="drawer"
app
:class="getSettingsTheme"
:mini-variant="mini"
>
<b>menu</b>
</v-navigation-drawer>
</template>
sitebar.vue
<template>
<v-toolbar fixed app clipped-right :class="getSettingsTheme">
<v-tooltip right>
<v-btn slot="activator" icon #click.stop="drawerMut">
<v-icon class="theme--light grey--text" color="grey lighten-1">menu</v-icon>
</v-btn>
<span>Show/Hide drawer</span>
</v-tooltip>
</v-toolbar>
</template>
vuex
export default new Vuex.Store({
state: {
drawer: true,
},
mutations: {
drawerMut(state) {
state.drawer = !state.drawer
}
},
getters: {
drawer(state) {
return state.drawer
},
}})

You can read the drawer state from the store and update to the store
in Vue we have computed setter please go through the docs.
if you use :value then on backdrop click the state/value of the drawer won't be updated, so use v-model with drawer as computed with setter and getter.
export default {
data() {
return {
mini: true,
getSettingsTheme: ''
}
}
computed: {
drawer: {
get() {
return this.$store.getters['drawer'];
},
set(val) {
this.$store.commit('drawerMut', val);
},
}
}
}
<template>
<v-navigation-drawer fixed
v-model="drawer"
app
:class="getSettingsTheme"
:mini-variant="mini">
<b>menu</b>
</v-navigation-drawer>
</template>

Related

How to create a reusable Vuetify Snackbar?

I'm trying to create a reusable alert/snackbar component. I declared them like this:
Snackbar.vue
<template>
<v-snackbar transition="true" timeout="2000" :show="`${show}`" :color="`${color}`" absolute top outlined right>
<strong>
{{ message }}
</strong>
</v-snackbar>
</template>
<script>
export default {
name: 'Snackbar',
props: {
show: String,
color: String,
message: String
}
}
</script>
<template>
<v-snackbar transition="true" timeout="2000" :show="`${show}`" :color="`${color}`" absolute top outlined right>
<strong>
{{ message }}
</strong>
</v-snackbar>
</template>
<script>
export default {
name: 'Snackbar',
props: {
show: String,
color: String,
message: String
}
}
</script>
I imported, and pass probs to them like so :
import Snackbar from '../../../components/Snackbar'
<Snackbar v-if="alert" color="alertColor" message="alertMessage" />
<Snackbar show="alert" color="alertColor" message="alertMessage" />
I've tried the 2 lines above, and these are the value of those 3 variables
alert = true
alertColor = green
alertMessage = create.vue?f4fe:631 DB error - insert_marketing_campaign: Duplicate entry 'TEST' for key 'MARKETING_CAMPAIGN_AK'
Result
I see no error in console, but see no snackbar either. pls help
8!
You aren't passing props values correctly, you have to do this instead:
ps. for illustration, i added an event listener(update) to deal with a close button(remove if you dont want to use)
<Snackbar :show="alert" :color="alertColor" :message="alertMessage" v-on:update="alert = $event" />
Snackbar.vue
I changed a few things here too:
the way you are using props in the attributes works, but if dont need to concatenate other values, keep it simple
prop show must be Boolean
added a beautiful button if you want to use
v-model controls snackbar exhibition, but you can't use the prop show directly on v-model, that's why we have a computed property "show2"(very creative) that read(get) the value of prop show and emit a custom event "update" if you want to change it(set)
<template>
<v-snackbar transition="true" timeout="2000" v-model="show2" :color="color" absolute top outlined right>
<strong>
{{ message }}
</strong>
<v-btn color="red" text #click="show2 = false">Close</v-btn>
</v-snackbar>
</template>
<script>
export default {
props: {
show: Boolean,
color: String,
message: String,
},
computed: {
show2: {
get() {
return this.show;
},
set(value) {
this.$emit("update", value);
},
},
},
};
</script>
Hope it helps! ;)
Check this codesandbox I made: https://codesandbox.io/s/stack-71244492-vuex-snackbar-klfunf?file=/src/store/index.js
You can set up a global snackbar in your App.vue file and update it with Vuex. In your vuex store you set up an snackbar object and create a simple update mutation and show action.
// store/index.js
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
snackbar: { active: false, color: "", message: "", timer: 4000 }
},
mutations: {
UPDATE_SNACKBAR(state, snackbar) {
state.snackbar = snackbar;
}
},
actions: {
showSnack({ commit }, { message, color, timer }) {
commit("UPDATE_SNACKBAR", {
active: true,
color: color,
message: message,
timer: timer
});
}
}
});
Then in your App.vue you set it up like this
<v-snackbar
v-model="snackbar.active"
:color="snackbar.color"
:right="true"
:bottom="true"
:timeout="snackbar.timer"
>
{{ snackbar.message }}
<template #action="{ attrs }">
<v-btn
color="white"
text
v-bind="attrs"
#click="snackbar.active = false"
>
Close
</v-btn>
</template>
</v-snackbar>
<script>
import { mapState } from "vuex";
export default {
name: 'App',
data: () => ({
drawer: false,
}),
computed: {
...mapState(["snackbar"])
}
}
</script>
Then you can trigger your snackbar from any component simply by doing this
// views/Home.vue
this.$store.dispatch('showSnack', {
message: 'Testing, main page.',
color: 'green darken-1'
})
// views/About.vue
this.$store.dispatch('showSnack', {
message: 'Hello from about!',
color: 'red darken-1',
timer: 2000
})

Vuex do not mutate vuex store state outside mutation handlers - Vuetify

I am building a nuxt app with Vuetify. My use case is that i want to implement a global bottom-sheet.
My code below works fine until when i click outside the sheet and it throws an error.
What am I missing below?
Error: [vuex] do not mutate vuex store state outside mutation handlers.
What i have tried so far.
<template>
<div>
<v-bottom-sheet v-model="$store.state.sheet">
<v-card>
<v-card-title>Hi there</v-card-title>
<v-card-subtitle>This is a demo sheet</v-card-subtitle>
</v-card>
</v-bottom-sheet>
<v-btn class="mt-5" #click="openSheet">Sheet</v-btn>
</div>
</template>
<script>
export default {
methods: {
openSheet() {
this.$store.commit("openSheet");
}
}
};
</script>
Store/index.js
export const state = () => ({
sheet: false
})
export const mutations = {
openSheet(state){
state.sheet = !state.sheet
}
}
When you clicked outsise, you tried to modify the state directly. You need to mofify it with a mutation, one way is to create a computed property and set it to v-model:
<template>
<div>
<v-bottom-sheet v-model="sheet">
<v-card>
<v-card-title>Hi there</v-card-title>
<v-card-subtitle>This is a demo sheet</v-card-subtitle>
</v-card>
</v-bottom-sheet>
<v-btn class="mt-5" #click="openSheet">Sheet</v-btn>
</div>
</template>
<script>
export default {
computed: {
sheet: {
get () {
return this.$store.state.sheet;
},
set (value) {
this.$store.commit("openSheet");
}
}
},
methods: {
openSheet() {
this.$store.commit("openSheet");
}
}
};

Creating a Dialog component and showing/hiding from parent

In Vue/Vuetify, how do we hide/show dialogs from parent? I'm trying to use v-model and here is a simplified version of my setup:
Parent component (just a button that triggers the child component to show)
<template>
<div>
<v-btn class="ma-2" outlined fab color="red" small #click.stop="editItem()">
<v-icon size="16">mdi-close-circle</v-icon>
</v-btn>
<user-dialog v-model="dialog" :eitem="editedItem" class="elevation-2" />
</div>
</template>
<script>
import UserDialog from "./UserDialog.vue";
export default {
components:{
UserDialog
},
data() {
return {
counter: 0,
dialog: false,
editedItem: {},
}
},
methods: {
editItem: function() {
this.counter++;
this.editedItem = Object.assign({}, {
title: 'some title' + this.counter,
details: 'some details for this item'
});
this.dialog = true;
},
},
}
</script>
Child component (basically a dialog box)
<template>
<v-dialog v-model="value" max-width="500px">
<v-card>
<v-card-title>
<span class="headline">A Dialog</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12>
<v-text-field v-model="eitem.title" label="Title"></v-text-field>
</v-flex>
<v-flex xs12>
<v-text-field v-model="eitem.details" label="Details"></v-text-field>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click.stop="save">Save</v-btn>
<v-btn color="blue darken-1" text #click.stop="close">Cancel</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script>
export default {
props: {
value: Boolean,
eitem: Object,
},
data() {
return {
editedItem: this.eitem,
}
},
methods: {
save() {
//perform save
this.$emit('input', false);
},
close() {
this.$emit('input', false);
},
},
}
</script>
This setup works, but give the following warning:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
But if act upon this advice and declare a data item in the child component and set v-model of the v-dialog to this data item, the dialog stops showing up upon click.
I perhaps understand why it does that, but cannot figure out a proper way of fixing this that doesn't show warnings. Can anyone help me with this?
Since Vue throws a warning when you mutate props, you should not use v-model with props. To handle this use the following pattern:
computed: {
propModel: {
get () { return this.value },
set (value) { this.$emit('input', value) },
},
},
Define computed property with getter, that returns props.value, and setter that emits input event (that will be successfully handled in parent, since you use v-model)
Don't forget to chage your template:
<v-dialog v-model="propModel" max-width="500px">
This works for me and do not need to create a computed data.
<v-dialog
width="600px"
:value="value"
#input="$emit('input', $event)"
>
</v-dialog>

How to commit mutations in a Vuex store, from a Vuetify list, VueJS

This follows on from my previous question : How to get states from a Vuex store from within a Vuetify list, VueJs
I understand how to get states from the store, but I am struggling commiting mutations from within a list.
Here is my list :
export default{
name: 'menuList',
data() {
return {
show: true,
items: [{
title: 'Do something',
icon: 'web',
event: function () {
this.$store.commit('UI/DoSomething', {
argument1: "argument1",
rootStore: this.$store
})
}
}
]
}
}
}
I have to have the event in a function as, if I don't, the commit will run straight away. I only want it to run when I click the button.
Here is my list creation :
<template>
<v-navigation-drawer v-model="show" right clipped app fixed hide-overlay permanent="true">
<v-list two-line>
<template v-for="(item, index) in items">
<v-list-tile :key="item.title" #click="item.event()">
<v-list-tile-action>
<v-icon>{{item.icon}}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title v-html="item.title"></v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-divider :key="index"></v-divider>
</template>
</v-list>
</v-navigation-drawer>
</template>
When I wrap the commit with a function (so it doesn't execute automatically), the keyword 'this' refers to the object, not the Vue.
I'm sure I'm missing something simple here, any ideas ?
Why do you want to store a function to commit a mutation in the data option?You can just store the information needed to commit a mutation.
export default{
name: 'menuList',
data() {
return {
show: true,
items: [{
title: 'Do something',
icon: 'web',
mutation: {
type: 'UI/DoSomething',
arguments:{
argument1: "argument1",
rootStore: this.$store
}
}
}
}
]
}
}
}
Then create a method as follows
commitMutation(mutation) {
this.$store.commit(mutation.type, mutation.arguments)
}
Then add a click listener with item.mutation as an argument to commitMutation method.
#click="commitMutation(itrm.mutation)"

As the state is updated in a computed property component does not update

I do not understand what i am doing wrong. I'm using Vuetify framework with nuxt.js on top of vue.js.
I wish that the navigation drawer's initial state to be open on the index page but close on the others. I can manage to have the desired result when i first load the page (if it's the index page the drawer is shown otherwise it's hidden), but when i change the page using the link in the drawer (nuxt js is using vue router in the background) the drawer preserves it's state.
I've made a quick middleware containing this:
export default ({store, route}) => {
const mitem = store.state.menu.filter(item =>
item.to.indexOf(route.name) >= 0
)[0]
const title = mitem ? mitem.title : 'Home'
store.commit('setPageTitle', title)
}
here's the store were are the state and mutations (the menu json file contains the entries with the following keys: { title, icon, to })
import menu from '~json/menu.json'
export const state = () => ({
menu,
drawer: true,
pageTitle: undefined
})
export const mutations = {
toggleDrawer: state => {
state.drawer = !state.drawer
},
setPageTitle: (state, title) => {
state.pageTitle = title
state.drawer = title === 'Home'
console.log(state.drawer)
}
}
And here's the layout
<template>
<v-app>
<v-navigation-drawer
persistent
v-model="drawer"
>
<v-list>
<v-list-tile
router
v-for="(item, i) in menu"
:key="i"
:to="item.to"
>
<v-list-tile-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>{{ item.title }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
</v-navigation-drawer>
<v-toolbar fixed>
<v-toolbar-side-icon #click.native.stop="toggleDrawer" />
<v-toolbar-title>{{ pageTitle }}</v-toolbar-title>
<v-spacer></v-spacer>
</v-toolbar>
<main>
<v-container fluid>
<nuxt />
</v-container>
</main>
</v-app>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
methods: {
...mapMutations([ 'toggleDrawer' ])
},
computed: {
...mapState([ 'menu', 'drawer', 'pageTitle' ])
}
}
</script>
The console.log shows me that the state get updated, but as the state.drawer changes the component is not updated and the drawer remains present.
Any idea on what i'm doing wrong/how to tackle that issue?
Thanks in advance
Seb
The v-navigation-drawer component by default watches for changes in $route. You can disable this functionality by adding the prop disable-route-watcher. Making this change will allow you to minutely control the state of the component.

Categories