Computed property "dialog" was assigned to but it has no setter [duplicate] - vue.js

This question already has answers here:
Vuex - Computed property "name" was assigned to but it has no setter
(5 answers)
Closed 4 years ago.
I am reproducing this code (Codepen):
<div id="app">
<v-app id="inspire">
<div class="text-xs-center">
<v-dialog
v-model="dialog"
width="500"
>
<v-btn
slot="activator"
color="red lighten-2"
dark
>
Click Me
</v-btn>
<v-card>
<v-card-title
class="headline grey lighten-2"
primary-title
>
Privacy Policy
</v-card-title>
<v-card-text>
Hello there Fisplay
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
flat
#click="dialog = false"
>
I accept
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</v-app>
</div>
The only difference between my real code and this one, is that I defined dialog in store/index.js (this in Nuxt.js) where I declared dialog an element of the state:
return new Vuex.Store({
state: {
dialog: false,
And then, in my current component I import that $store.state.dialog flag:
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState([
'dialog'
]),
}
</script>
Whenever I click on the button, I get this error message:
[Vue warn]: Computed property "dialog" was assigned to but it has no
setter.
How to fix this? Any alternative?

You need to use the Vuex mutation to update the state.
https://vuex.vuejs.org/guide/mutations.html
In your example,
Your click event should be handled in the methods #click="handleClick"
methods: {
handleClick() {
this.$store.commit('openDialog')
}
In your store.js
mutations: {
openDialog(state) {
state.dialog = true
}
}

mapState will only create getters. You should define a mutation in your vuex store, which will be able to change the state.
Just add this to your store:
...
mutations: {
SET_DIALOG_FLAG_FALSE (state) {
state.dialog = false;
},
//optional if you do not want to call the mutation directly
//must important different is, that a mutation have to be synchronous while
//a action can be asynchronous
actions: {
setDialogFalse (context) {
context.commit('SET_DIALOG_FLAG_FALSE');
}
}
If you want to work with mapMutations/mapAction in your component:
import { mapMutation, mapActions } from vuex;
...
//they are methods and not computed properties
methods: {
...mapMutations([
'SET_DIALOG_FLAG_FALSE'
]),
...mapActions([
'setDialogFalse'
])
}
Now in your v-btn you can call the action or mutation.
<v-btn
color="primary"
flat
#click="this.setDialogFalse"> I accept </v-btn>

Related

Close a Vuetify v-dialog in a custom component when using v-slot:activator

I am trying to create a custom component that wraps a Vuetify v-dialog.
I am having trouble with closing the dialog using a button inside the dialog.
I've tried many things, like #emit('input', false), this.value = false or using a local data value instead of value, but nothing seems to work.
My dialog looks like this (simplified):
// file: DeleteDialog.vue
<template>
<v-dialog :value="value" #input="$emit('input', $event)" width="550">
<template v-slot:activator="{ on, attrs }">
<slot
name="activator"
v-bind:on="on"
v-bind:attrs="attrs"></slot>
</template>
<v-btn #click="closeDialog">
Close
</v-btn>
</v-dialog>
</template>
<script>
export default {
props: {
value: Boolean,
},
methods: {
closeDialog() {
this.$emit('input', false);
},
},
};
</script>
When using the dialog like this, how do I get the Close-button to work?
<DeleteDialog>
<template v-slot:activator="{on, attrs">
<v-btn v-on="on" v-bind="attrs">
Show dialog
</v-btn>
</template>
Are you sure you want to delete this user?
</DeleteDialog>
The prop you pass value is responsible for showing or hiding your v-dialog inside DeleteDialog.vue.
So when clicking the button close, we will emit an event close that will maake the parent of DeleteDialog change the prop value it passed to it as false.
// in your parent component
<DeleteDialog :value="show_dialog" #close="show_dialog = false>
in your deleteDialog
//in delete dialog
<template>
<v-dialog :value="value" width="550">
<v-btn #click="closeDialog">
Close
</v-btn>
</v-dialog>
</template>
<script>
export default {
props: {
value: Boolean,
},
methods: {
closeDialog() {
this.$emit('close');
},
},
};
</script>
I think that is more suitable way of achieving what you want

Pass click function with input v-model params

I have a btnSmall component with this props and click
<v-btn
class="white--text btn_main_product"
color="red darken-1"
#click="click"
>
<slot/>
</v-btn>
props: {
click: Function
}
I use this component like this in another component.
<template>
<v-card
class="pa-4"
>
<v-text-field
v-model="lname"
></v-text-field>
<v-btn :click="clickAccept(lname)">accept/v-btn>
</v-card>
</template>
<script>
import BtnSmall from "../buttons/btnSmall";
export default {
name: "editName",
components: {BtnSmall},
data() {
return {
lname: ''
}
},
props: ['clickAccept']
}
</script>
and use this component in the page.
after use <edit-name :click-accept="nameClickAccept"/> whit this method.
nameClickAccept(fname, lname) {
console.log(lname)
}
my method run with change text-field i cant use click.
i need run method with click on the btn.
The vue-approach would be to emit an event in the btnSmall component and react on that event in the parent component.. This: https://v3.vuejs.org/guide/component-custom-events.html#event-names shows you how you can do it.

Vuex mutate object best practices

I'm developing a module that enables users to place remarks on an object. I use a Vuex store and a component to visualize the remarks. I've pasted the code below.
Thing is I bind the text area that displays the remark body to a v-model that is the property body of the remark object. Since I return the array of remarks from the store with the getremarks getter any changes to the remark object properties are directly changed in the store as well since it is the same object that gets passed around. I thereby effectively bypass the entire action/mutation pattern Vuex wants me to implement.
I can always pass the mutated remark to an action and mutation in the store but that means I'm mutating an object that is allready mutated. I was wondering if there are any best practices around?
Store
import Vue from 'vue'
const model = {
state: {
remarks: [],
},
mutations: {
SET_REMARKS(state, remarks) {
state.remarks = remarks;
}
},
getters: {
getRemarks(state) {
return state.remarks;
},
getRemarkById: (state) => (id) => {
return state.remarks.find(remark => remark.model_remark_id === id);
}
},
actions: {
}
}
export default model;
Component
<template>
<v-list two-line expand>
<v-list-group v-for="remark in remarks" :key="remark.model_remark_id">
<template v-slot:activator>
<v-list-item-icon>
<v-icon v-text="icon" :color="remark.priority === 1 ? 'red' : remark.priority === 2 ? 'orange' : '#1478c7'" />
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title><b>{{ remark.created_by_user.name }}</b></v-list-item-title>
<v-list-item-subtitle>Created at {{ moment(remark.created_at).format('DD-MM-YYYY HH:ss:mm') }}</v-list-item-subtitle>
</v-list-item-content>
</template>
<v-list-item>
<v-list-item-icon>
</v-list-item-icon>
<v-list-item-content>
<v-row>
<v-col>
<v-textarea
class="mx-2"
v-model="remark.body"
auto-grow
/>
</v-col>
</v-row>
<v-row>
<v-col align="right">
<v-btn
color="primary"
elevation="2"
#click="remarkDone(remark)"
>Done</v-btn>
<v-btn
color="primary"
elevation="2"
#click="remarkUpdate(remark)"
>Update</v-btn>
</v-col>
</v-row>
</v-list-item-content>
</v-list-item>
</v-list-group>
</v-list>
</template>
<script>
import { mapGetters, mapActions } from "vuex";
export default {
data: () => ({
icon: "mdi-comment"
}),
computed: {
remarks: function () {
return this.getRemarks();
},
},
methods: {
...mapGetters([
"getRemarks",
"getRemarkById"
]),
remarkUpdate(remark) {
console.log(remark);
const x = this.getRemarkById()(remark.model_remark_id);
console.log(x);
}
},
};
</script>
I think the key principle to keep in mind here is separation of concerns. Let Vuex handle all mutations of the state and let your Vue component simply fetch the remarks from the state. The state is reactive, so if you call a mutation from your component to change your remarks, you want the component to simply react to that change and render the updated markup.
Also, you should use mapGetters to map your getters to computed properties, not methods. I'm also not sure if you need getRemarkById getter in updateRemark() since you already have the remark as an argument.
export default {
computed: {
...mapGetters({ remarks: 'getRemarks' })
},
methods: {
updateRemark(remark) {
// here you should commit a mutation to update your remark
this.$store.commit('updateRemark', remark)
}
}
}

Vue js - How to use props in data and methods

I am new in vue js , I am passing data from parent component to child one using props and I can use it in child normally but I can't use it in data of child component
parent
<template>
<div>
<show-question :qdata="question" v-if="question"></show-question>
<h1 v-if="!question"> some error </h1>
</div>
</template>
<script>
import ShowQuestion from './ShowQuestion';
export default {
created(){
axios.get(`/api/question/${this.$route.params.slug}`)
.then(res => {
this.question = res.data.data
})
},
data(){
return {
question : {},
}
},
components:{
ShowQuestion,
},
}
</script>
child
<template>
<v-container>
<v-card>
<div>
<v-card-title class="blue--text"
>{{ data.title }}
<v-spacer></v-spacer>
<v-btn color="teal white--text">5 Replies</v-btn>
</v-card-title>
<v-card-subtitle
> {{data.uid}} {{ data.user }} said {{ data.created_at }}</v-card-subtitle
>
</div>
<v-card-text>{{ data.body }}</v-card-text>
<v-card-actions v-if="own">
<v-btn icon text>
<v-icon color="orange">create</v-icon>
</v-btn>
<v-btn icon text>
<v-icon color="red">delete</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-container>
</template>
<script>
export default {
props: ['qdata'],
data(){
return {
own: User.own(this.qdata.uid),
};
},
};
</script>
this.qdata.uid is always be undefined in console, although it supposed it have values and I can saw it from child template
enter image description here
Your show-questioncomponent is mounted early on because v-if="question" is true. When show-question is mounted, your api call hasn't had a chance to finish, so question is the same as the initial value {}, which is why uid is undefined.
I would change question : {} to question: null, then the child component will only be mounted when there's a question (after the api call).
This is simply because if you check the truthy of an object it will always return true even if the object is empty, which results in the component being rendered before the API call has finished.
Instead, you can check if it's empty or not by converting it to an array and check it's length value, i.e. Object.entries(question).length or simply use the lodash helper method: _isEmpty(question).
Also a quick side note: it's cleaner to use v-else after v-if when you want to render something or the other instead of explicitly negating the value in another v-if, though they're required to be direct siblings.

Vue: Data is not changing with Vuex getter?

I have a modal component that I was triggered when different components mutate the triggerModalState field. I have a getter in my Vuex store called getFulfillmentModalState. The modal is driven off of the local data field called dialog, which I have set to be the value of the getter this.$store.getters.getFulfillmentModalState. I am checking the value of this.$store.getters.getFulfillmentModalState, and it is true after I trigger the modal, but the dialog data is still false. What am I doing wrong here?
<template>
<div class="fulfillment-modal-component">
<v-row justify="center">
<v-dialog v-model="dialog" persistent max-width="290">
<v-card>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="rgba(53, 87, 151, 0.85)" text #click="changeModalState(true)">Cancel</v-btn>
<v-btn color="rgba(53, 87, 151, 0.85)" text #click="changeModalState(false)">Continue</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</div>
</template>
<script>
import {mapState} from 'vuex';
import store from './../../store/store';
export default {
name: 'fulfillment-modal',
mounted() {
},
data() {
return {
dialog: this.$store.getters.getFulfillmentModalState
}
},
}
</script>
dialog isn't reactive in this case because it's declared in data(). Move dialog to computed to make it reactive:
//...
export default {
data() {
return {
//dialog: /*..*/ // move to computed
}
},
computed: {
dialog() {
return this.$store.getters.getFulfillmentModalState
}
}
}
demo