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

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

Related

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;
},
}

VueJS Watcher is not triggered while watching deep object changes

I got a codesandbox that reproduces my problem: codesandbox example watcher not triggering.
I am working on a component that relies on an object with data that can be dynamically added to the object, so for example, in a seperate .js file, I am exporting the following object:
export default {
defaultSection1: {
displayName: 'Action',
},
defaultSection2: {
displayName: 'Thriller',
},
}
I import this object in my component.
I got a debouncer setup from Lodash so that when data changes, it only fires the watcher once after two seconds. The watcher IS triggered perfectly fine for the data that is already in the object (type in the input text box in my example, and the watcher is triggered). But when I dynamically add data to the object, the watcher is not triggered at all. Only when I change routes back and forth, the data is updated, but the watcher is not triggered. Why is this? What can I do so that the watcher is triggered when data is dynamically being added to an object.
methods: {
fetchAndUpdateData(){
console.log('Fetching data...')
},
addCustomSection(){
//The watcher should be triggered when this function is called
const newSectionId = Math.floor(Math.random() * 1000);
this.data[newSectionId] = {
displayName: 'Custom'
}
}
},
computed: {
dataWatcher() {
return this.data;
},
updateData() {
return debounce(this.fetchAndUpdateData, 2000);
},
},
watch: {
dataWatcher: {
handler() {
console.log('Watcher triggered')
this.updateData();
},
deep: true,
},
},
Why is the watcher not triggered, when clearly the data is being changed?
Another thing I noticed that is quite strange, is that in Vue 3.0, the watcher IS triggered with exactly the same code.
Codesandbox in Vue 2.6.11, watcher not triggering.
Codesandbox in Vue 3.0, watcher IS triggered with exactly the same code.
In vue 2 there's reactivity issue when updating an item in an array or a nested field in an object, to solve this you've to use this.$set() method :
this.$set(this.data,newSectionId, {displayName: 'Custom'})
this issue is solved in Vue 3. and you could just do :
const newSectionId = Math.floor(Math.random() * 1000);
this.data[newSectionId] = {
displayName: 'Custom'
}

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

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!

How to call a function after selecting an option from a select element in Vue?

I have a couple of options in a <select> element. How can I call a function when I choose an option?
Here is what I have so far in terms of the JS:
export default {
name: 'Dropdown',
methods: {
onChange() {
logout();
},
},
computed: {
logout() {
console.log('logout);
},
},
};
My select menu looks like this: <select #change="onChange">...
When I select an option, I receive the following error: logout is not defined. Any ideas?
you need to use this to reference the instance, so it will be this.logout();
EDIT:
You need to create an onLogout in the methods property if you want to do something particular, a computed property is a reactive property that return something, it isn't really meant to do something that handle the logout.
As i see your logout computed property would be more about getting the current status of the user if he's logout or not, but it would be better to rename it.
So if you want to do something particular when the select change(like a logout method)
you need to adapt your component this way:
export default {
name: 'Dropdown',
methods: {
onChange() {
this.logout();
},
logout() {
console.log('logout called');
},
},
computed: {
userStatus() {
// return a state or something, but must return something
return ...
},
},
};
I invite you to read this documentation to have better idea about computed property and methods
https://v2.vuejs.org/v2/guide/computed.html#Computed-Properties
https://v1.vuejs.org/guide/events.html
The logout function should be on the methods object top and you reference it using this.logout()

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