How can i do a setter to fix computed propery problem - VueJS - javascript

Im trying to fix this error where i cant close the dialog by clicking the X button on the top right, but i can close it by clicking the "Agregar" or "Cancelar" button.
This is how i sync my Dialog
<el-dialog title="Agregar Persona" :visible.sync="getAgregarPersonaDialog">
And here is my Computed Property
computed: {
getAgregarPersonaDialog() {
return this.$store.state.agregarPersonaDialog;
}
}
This is how i change the value of my state
setAgregarPersonaDialogo(state) {
state.agregarPersonaDialog = !state.agregarPersonaDialog
},
And in my state i have this atribute
export default new Vuex.Store({
state: {
agregarPersonaDialog: false
}
});
This is the error i have everytime i click the X button on the dialog
Computed property "getAgregarPersonaDialog" was assigned to but it has no setter.

When using Vuex, if your need to mutate any data on state arises then you absolutely need an action that calls a mutation to mutate state.
This is the most important use case of Vuex, to keep track of actions that mutates state
what you did is absolutely wrong.
To achieve your goal, you need two things.
an action
a mutation
action: {
// The first parameter is an object, so it be destructured
// for simple access
doSomething({ commit }, payload){
// commit a change by calling mutation, second parameter
// is the arguments you passed to your action
commit('mutateData', payload)
}
},
mutations: {
mutateData(state, payload) {
// You can instead commit the argument
// I.e state.data = payload
state.agregarPersonaDialog = !state.agregarPersonaDialog;
}
},
You can then dispatch your actions in any components
methods: {
changeValue(){
this.$store.dispatch('doSomething', 'this is an arg');
// Note I passed an argument just for illustration
// in your case, you don't have to
}
}
Now your code should work

ti change the state you need
1. action
2. Mutator
example in store.js
state: {
agregarPersonaDialog: false,
},
// value is data what you send from your componet
action: {
changeState(context){
context.commit('changeValue')
},
mutations: {
changeValue(state) {
state.agregarPersonaDialog = !state.agregarPersonaDialog;
}
},
---------------------
in your compoent create method
methods: {
changeValue(){
this.$store.dispatch('changeState');
}
}
try this!

Related

Vue2 change object property from method param passing into child props

oi, this one seems so simple but it's giving me a headache.
I have a child component with a property passed down:
<dialog-child requests='requests'/>
the passed prop, is an object obtaining varied booleans. The dialog is v-modeled to in this case,
<dialog v-model='request.deleteItem'>
requests { deleteItem: false, editItem: false, syncItem: false, }
When I click on the delete button, I want to make a request to delete an item, and pull up this dialog component. This works fine if i simply change the bool in the object to true, but I need more control by passing the #click to a method and passing a parameter.
<btn #click='makeRequest(deleteItem)'>Activate Dialog</btn>
so in the method, I need to figure out how to say that the passed deleteItem, is request.deleteItem and then I would make it true.
makeRequest(requested){
//somewhow say
this.requests.requested = true
}
How could I pass in the parameter to take control of the objects property?
I could do a long form of multiple if checks, if requested = '' then make this prop true, but that feels gross.
I also need to pass in a second param, item after I figure this out - so to pass in two params do i just say methodName(param1, param2) and on click method(item1, item2) or do I need to create an object like method({item1, item2})?
To have everything nice and neat :) but:
For that we need option "to emit event on child component" but Vue.js does not work like that. So try this:
<template>
<div>
<dialog-child requests='requests' #resetRequest="resetRequest()"/>
<btn #click='makeRequest("deleteItem")'>Delete</btn>
</div>
</template>
<script>
import DialogChild from 'Dialog.vue'
export default {
components: { "dialog-child" : DialogChild },
data() {
return {
requests: {
makeRequest: false, //New Field
deleteItem: false,
editItem: false,
syncItem: false
}
}
},
methods: {
makeRequest(action) {
//So here you can create new request Object with whatever you want. Use action argument to check what you want to do here...
let newRequests = {
makeRequest: true,
deleteItem: true,
editItem: false,
syncItem: false
}
//Then this newRequest object need to copy to this.requests, this will update request object reference, and trigger "watch" in child component
this.requests = newRequests;
},
resetRequest() {
//Request object is again updated with new reference, but makeRequest is false so it will not trigger action in Dialog Child component
this.requests = {
makeRequest: false, //Now this is false,
deleteItem: false,
editItem: false,
syncItem: false
}
}
}
}
</script>
//And child component should be like this
<template>
<div>
</div>
</template>
<script>
export default {
props: {
requests: { Type: Object }
},
watch: {
requests(newVal) {
if(newVal && newVal.makeRequest) {
this.doStuff()
}
}
},
methods: {
doStuff() {
//So after doing what you want, you need to make event to reset requests
this.$emit('resetRequests');
}
}
}
</script>
i fixed this by changing the param to a string, and removing the request for a second item. Onwards to a item pass in too.

Vue 2 watch fails for mixin data property, but works if the component has the data property

I am trying to move some functionality to a vue mixin from the component, to be able to use it in multiple components.
This (simplified version of the code) works:
export default {
data() {
return {
file: {},
audioPlayer: {
sourceFile: null,
},
};
},
watch: {
'audioPlayer.SourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}
}
But if I move the audioPlayer data object to a mixin, the watch does no longer fire.
Is this expected behavior?
N.b. I resolved this by directly making the 'file' data property into a computed value, which works in this particular case, but the behavior is still strange.
You need a lowercase s. sourceFile not SourceFile
watch: {
'audioPlayer.sourceFile': function (nextFile) {
console.log('new sourceFile');
this.$data.file = nextFile;
},
}

Vue: Radio does not update its value when data changes asynchronously

I do not understand why my new code does not work. I was able to extract a minimum reproducible case. When the created() sets a data synchronously, it works well and an article radio is displayed. When I surround it with timeout, then the blog stays selected. Vue 2.6.12
The bug in this code has been fixed, but this was not the cause for my troubles because my real code is different. My problem is that the radio button is not checked when it should be after the reactive data is changed.
<Radio
v-model="type"
identifier="article"
class="pl-3"
label="article"
name="type"
/>
<Radio
v-model="type"
identifier="blog"
class="pl-3"
label="blog"
name="type"
/>
<div>Selected {{ type }}</div>
data() {
return {
type: "blog",
};
},
created() {
setTimeout(function () {
this.type = "article";
console.log(this.type);
}, 800);
},
This makes my head explode because a similar code in different component works well.
UPDATE:
my original code, that does not work, is
computed: {
blog() {
return this.$store.getters.BLOG;
},
},
watch: {
blog() {
this.type = (this.blog.info.editorial) ? 'article' : 'blog';
},
created() {
this.$store.dispatch('FETCH_BLOG', { slug: this.slug });
},
Relevant source code:
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/views/item/WriteBlog.vue
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/components/atoms/Radio.vue
https://github.com/literakl/mezinamiridici/blob/234_editorial_team/spa/src/modules/vuex/items.js
All you need is to change your function to an arrow function because it isn't point your data like this
setTimeout(() => {
this.type = "article";
console.log(this.type);
}, 800);
The problem is the selected property in Radio.vue is only set equal to value in the created() hook. When the setTimeout() occurs in the parent component, Radio.vue's v-model property is changed, which updates its value property, but its selected property is not automatically updated to match.
The solution is to replace the created() hook change with a watcher on value that updates selected:
// Radio.vue
export default {
created() {
// ⛔️ Remove this
//if (this.value) {
// this.selected = this.value
//}
},
watch: {
value: {
handler(value) {
this.selected = value
},
immediate: true,
},
},
}
demo
I assume your original code does not set the type in vue's data function, so it will not reactive when you assign this.type to a new value.
Manage state in a form is complicated, check out this library: https://github.com/vue-formily/formily and maybe it helps you easier to work with form, it will let you separate the form definition from vue component that makes it reusable, and it will manage the state for you...
Here is a small demo for your problem: https://codepen.io/hqnan/pen/YzQbxxo

Running a callback in a Vue component and waiting for result before changing state

I have a Vue component called aui-button that is used like:
<aui-button classes="btn-primary test-class" :callback="test">Test Button</aui-button>
I'm trying to design the component to be able to run any given callback without the callback requiring any structural changes or special return values; basically, I want to just give it some code, and have it run it. I pass the callback as a prop and call it. Easy.
props: ['classes', 'callback'],
methods: {
runCallback() {
this.callback();
}
}
What I'm stuck on is how to change the state data of the button based on the callback given those constraints.
data: function() {
return {
loading: false
}
},
How can I change the value of a specific data value on the component after callback execution given that I'd like to have zero say over what the callback argument is doing? Ideally these have a wide array of uses, from API calls to simple value changes.
Here's another answer in response to your comment. If you really wanted to implement it that way then there's nothing stopping you.
The problem is that the callback will be asynchronous (otherwise, what's the point of setting a loading state), so the component needs to be told when the asynchronous callback has completed.
You could define your callback functions so that they take a done() callback.
AuiButton.vue
props: ['classes', 'callback'],
data() { return { loading: false}},
methods: {
runCallback() {
this.loading = true;
this.callback(this.doneCallback);
},
doneCallback() {
this.loading = false;
},
},
Parent Component
<aui-button classes="btn-primary test-class" :callback="test">Test Button</aui-button>
...
methods: {
test(doneCallback) {
doSometingAsync(someData, (error, result) => {
doneCallback();
// do something with result..
})
},
Perhaps a better solution is to use events, but give the click event a done callback:
AuiButton.vue
props: ['classes'], // don't need to pass the callback as a prop anymore.
data() { return { loading: false}},
methods: {
runCallback() {
this.loading = true;
this.$emit('click', this.doneCallback);
},
doneCallback() {
this.loading = false;
},
},
Parent Component
<aui-button classes="btn-primary test-class" #click="test">Test Button</aui-button>
...
methods: {
test(doneCallback) {
doSometingAsync(someData, (error, result) => {
doneCallback();
// do something with result..
})
},
Rather than pass a callback to your button component, it is recommended practice to make your component $emit an event to the parent. The parent can then run whatever code it wants in the event handler.
Then, you should pass a loading prop to your component to control its loading state. e.g:
AuiButton.vue
<button :classes="classes" :disabled="loading" #click="$emit('click') ...>
...
props: {
classes: String,
loading: {
type: Boolean,
default: false
},
Parent component
<aui-button classes="btn-primary test-class" :loading="loading" #click="buttonClick">
Test Button
</aui-button>
...
data() { return {
loading: false,
} },
methods: {
buttonClick() {
this.loading = true;
doSometingAsync(someData, (error, result) => {
this.loading = false;
// do something with result..
})
},
Now your button component doesn't need to do anything clever - it's told whether it should be in the loading state and it tells its parent whenever it is clicked.

Reactivity problem with mapGetters only on first load

I am using the mapGetters helper from VueX but i have some problem only on the first load of the page, it's not reactive...
Let me show you :
my html template triggering the change :
<input type="number" value="this.inputValue" #change="this.$store.dispatch('setInputValue', $event.target.value)">
my store receiving the value
{
state: {
appValues: {
inputValue: null
},
},
getters: {
getInputValue: (state) => {
return state.appValues.inputValue;
},
},
mutations: {
setInputValue(state, value) {
state.appValues.inputValue = value;
},
},
actions: {
setInputValue(context, payload) {
return new Promise((resolve, reject) => {
context.commit('setInputValue', payload);
resolve();
});
},
}
}
and then my component listening the store :
import {mapGetters} from 'vuex';
computed: {
...mapGetters({
inputValue: 'getInputValue',
}),
}
watch: {
inputValue: {
deep: true,
immediate: true,
handler(nVal, oVal) {
console.log("inputValue", nVal, oVal);
}
},
}
So now, when i first load the page I get this console.log "inputValue" null undefined which is totally normal because as I have nothing in my store it gaves me the default value null.
But now it's the weird part. I start changing the input value and I don't have nothing appearing in my console. Nothing is moving...
Then I reload the page and on the load I get this console.log "inputValue" 5 undefined (5 is the value I entered previously) so as you can see, when I was changing the input previously, it was well keeping the value in the store but the computed value was not updating itself...
Ans now, when I change the value of the input I have my console log like this "inputValue" 7 5 so it's working as I would like it to work from the start...
What do I do wrong? Why on the first load the computed value not reactive?
Thanks for your answers...
I think the best way to solve this issue is to store a local variable with a watcher, and then update vuex when the local is changed:
On your component:
<input type="number" v-model="value">
data() {
return {
value: ''
};
},
computed: {
...mapGetters({
inputValue: 'getInputValue'
})
}
watch: {
value(value){
this.$store.dispatch('setInputValue', value);
},
inputValue(value) {
console.log('inputValue', value);
}
},
created() {
// set the initial value to be the same as the one in vuex
this.value = this.inputValue;
}
Please take a look at this sample: https://codesandbox.io/s/vuex-store-ne3ol
Your mistake is, you are using this keyword in template. One shouldn't use this in template code.
<input
type="number"
value="inputValue"
#change="$store.dispatch('setInputValue', $event.target.value)"
>
Bonus tip: It is redundant to use a getter to return the default state
if you can just use mapState to return the state.
There are a few small mistakes in the template. It should be this:
<input type="number" :value="inputValue" #change="$store.dispatch('setInputValue', $event.target.value)">
I've removed the this. in a couple of places and put a : out the front of value. Once I make these changes everything works as expected. The this.$store was causing console errors for me using Vue 2.6.10.
I would add that you're using the change event. This is the standard DOM change event and it won't fire until the field blurs. So if you just start typing you won't see anything happen in the console. You'd be better off using input if you want it to update on every keystroke. Alternatively you could use v-model with a getter and setter (see https://vuex.vuejs.org/guide/forms.html#two-way-computed-property).
My suspicion is that when you were reloading the page that was triggering the change event because it blurred the field.
Ok, so ... I found the problem and it was not relative to my examples, I can't really explain why, but I'll try to explain how :
In my store I have the next method :
mutations: {
deleteAppValues(state) {
state.appValues = null;
}
}
I was using this one on the Logout, or when the user first comes on the pageand was not logged-in... So what was going-on?
The User first load the page, the store is initializing well, and the index inputValue is initialized with null value, so it exists...
... But as the User is not logged, I destroy the store so now the inputValue is not equals to null, it just doesn't exist...
Trying to use mapGetters on something that don't exists, the reactivity won't work, so if I dispatch a change, the store key will be created, but as the mapGetters was initialized with an inexisting key, it doesn't listen the reactivity...
After reloading the page, the key now exists in the store so the getter can be attached to it and so now everything working fine...
This is exactly the explaination of what was going wrong about my code... So to make it works fine, I just changed my destruction mutation to :
mutations: {
deleteAppValues(state) {
state.appValues = {
inputValue: null,
};
}
}
Like this, the inputValue key of the store object will always exists and so the getter won't lose his reactivity...
I tryed to make a simple concise question but that made me forgot the bad part of my code, sorry.

Categories