Undesirable watcher execution order - vuejs2

I have a dropdown component that has two props: 'val', 'options' (array of objects). The component is driven off local versions of these values 'locVal' and 'locOptions' which are updated through the two public watched props.
The parent needs to be able to provide the 'options' array and/or the 'val' at any time in any order and my watchers decide how to update the internal values so they are always in a consistent state:
watch: {
val() {
// validate 'val' against 'locOptions' and set 'locVal' if match
},
options() {
// validate 'locVal' against 'options' and leave 'locVal' alone
// if still match otherwise set to null
// also set 'locOptions' to 'options'
}
},
There is a problem however with the order in which Vue calls the watched functions. They are not called in the order the values where changed but instead in the order of declaration.
Assume the initial internal values:
locOptions = [
{ val: 1, text: 'one' },
{ val: 2, text: 'two' )
];
locVal = 1;
// parent sets the props in this order
val = 3;
options = [
{ val: 3, text: 'three' },
{ val: 4, text: 'four' )
];
// desired outcome
'locVal' should be null
'val' is set to mirror 'locVal' with two way binding
Here the 'val' is first matched to 'locOptions'. It fails to match so to enforce validation the component sets 'locVal' and 'val' to null (the only viable value if no match). After, when the 'options' is matched against the 'locVal' it leaves 'locVal' alone since null is still a viable value for the new set.
// setting the props in opposite order
options = [
{ val: 3, text: 'three' },
{ val: 4, text: 'four' )
];
val = 3;
// desired outcome
'locVal' should be 3
'val' is set to mirror 'locVal' with two way binding
In this scenario 'options' are first matched against the 'locVal'. It fails as '1' is not in the new list so 'locVal' and 'val' are set to null. Then 'val' is matched to 'locOptions'. It matches thus 'locVal' and 'val' get set to 3.
How can I get Vue to observe the order in which these props where changed to get the desired behavior?

Vue has (had?) an undocumented API for watchers called sync.
Instead of waiting next tick to call the watchers, it calls them immediately. It's not recommended, and could lead to infinite loops if used inappropriately.
watch: {
val: {
handler () {
// validate 'val' against 'locOptions' and set 'locVal' if match
},
sync: true
},
options() {
handler() {
// validate 'locVal' against 'options' and leave 'locVal' alone
// if still match otherwise set to null
// also set 'locOptions' to 'options'
},
sync: true
}
},

Related

How to not trigger watch when data is modified on specific cases

I'm having a case where I do wish to trigger the watch event on a vue project I'm having, basically I pull all the data that I need then assign it to a variable called content
content: []
its a array that can have multiple records (each record indentifies a row in the db)
Example:
content: [
{ id: 0, name: "First", data: "{jsondata}" },
{ id: 1, name: "Second", data: "{jsondata}" },
{ id: 2, name: "Third", data: "{jsondata}" },
]
then I have a variable that I set to "select" any of these records:
selectedId
and I have a computed property that gives me the current object:
selectedItem: function () {
var component = this;
if(this.content != null && this.content.length > 0 && this.selectedId!= null){
let item = this.content.find(x => x.id === this.selectedPlotBoardId);
return item;
}
}
using this returned object I'm able to render what I want on the DOM depending on the id I select,then I watch this "content":
watch: {
content: {
handler(n, o) {
if(o.length != 0){
savetodbselectedobject();
}
},
deep: true
}
}
this work excellent when I modify the really deep JSON these records have individually, the problem I have is that I have a different upload methord to for example, update the name of any root record
Example: changing "First" to "1"
this sadly triggers a change on the watcher and I'm generating a extra request that isnt updating anything, is there a way to stop that?
This Page can help you.
you need to a method for disables the watchers within its callback.

Accessing data from array in rendered list in vue.js

I am trying to use a property from a rendered list which may change depending on if a checkbox is filled or not. However, mathSkill and scienceSkill always show 0. I feel like I'm doing something very wrong in trying to access booleanValue but I am not sure what else I could put in the if statement to allow it to update the values. Thank you in advance if you have any insight!
const app = new Vue({
el: '#app',
data: {
abilities: [
{ value: 'math', id: 'math', booleanValue:'no' },
{ value: 'science', id: 'science', booleanValue:'no'},
{ value: 'english', id: 'english', booleanValue:'no'},
],
// VARIABLES
mathSkill: 0,
scienceSkill: 0,
},
computed: {
addToMath: function() {
if (this.abilities[0] === 'yes' )
mathSkill = mathSkill +1,
scienceSkill = scienceSkill + 1;
}
}
I don't know exactly what you are trying to accomplish.
Don't define the variables, if you are going to calculate them.
Use .filter to make a new array based on some condition, and use .length to get how many objects in that array
computed: {
matchSkill() { return this.abilities.filter(ability => ability.booleanValue === "yes").length},
}
Example code

Computed not reactive?

I wrote this code to return a list of skills. If the user already has a specific skill, the list-item should be updated to active = false.
This is my initial code:
setup () {
const user = ref ({
id: null,
skills: []
});
const available_skills = ref ([
{value: 'css', label: 'CSS', active: true},
{value: 'html', label: 'HTML', active: true},
{value: 'php', label: 'PHP', active: true},
{value: 'python', label: 'Python', active: true},
{value: 'sql', label: 'SQL', active: true},
]);
const computed_skills = computed (() => {
let result = available_skills.value.map ((skill) => {
if (user.value.skills.map ((sk) => {
return sk.name;
}).includes (skill.label)) {
skill.active = false;
}
return skill;
});
return result;
})
return {
user, computed_skills
}
},
This works fine on the initial rendering. But if I remove a skill from the user doing
user.skills.splice(index, 1) the computed_skills are not being updated.
Why is that the case?
In JavaScript user or an object is a refence to the object which is the pointer itself will not change upon changing the underling properties hence the computed is not triggered
kid of like computed property for an array and if that array get pushed with new values, the pointer of the array does not change but the underling reference only changes.
Work around:
try and reassign user by shadowing the variable
The computed prop is actually being recomputed when you update user.skills, but the mapping of available_skills produces the same result, so there's no apparent change.
Assuming user.skills contains the full skill set from available_skills, the first computation sets all skill.active to false. When the user clicks the skill to remove it, the re-computation doesn't set skill.active again (there's no else clause).
let result = available_skills.value.map((skill) => {
if (
user.value.skills
.map((sk) => {
return sk.name;
})
.includes(skill.label)
) {
skill.active = false;
}
// ❌ no else to set `skill.active`
return skill;
});
However, your computed prop has a side effect of mutating the original data (i.e., in skill.active = false), which should be avoided. The mapping above should clone the original skill item, and insert a new active property:
const skills = user.value.skills.map(sk => sk.name);
let result = available_skills.value.map((skill) => {
return {
...skill,
active: skills.includes(skill.label)
}
});
demo
slice just returns a copy of the changed array, it doesn't change the original instance..hence computed property is not reactive
Try using below code
user.skills = user.skills.splice(index, 1);

Make react-select option unsearchable

I'm using react-select and have one option with a blank name and the value of not set and another with the name and value of notification. This means my options array looks something like this (though with many more options):
const options = [
{
label: '',
value: 'not set'
},
{
label: 'Notification',
value: 'notification'
}
];
Because of this, when users are looking for the notification option, they often just type not and then are shown the blank option.
I do want this option to show in the dropdown menu itself if someone is just thumbing through it (with arrow keys) but is there a way I can prevent certain options from showing in the search? Maybe a key I can include in the option?
I recommend you to use the props filterOption to achieve your goal. See more how to use it here.
You can either decide to filter by label only instead of value or add a key for each searchable option like this:
const options = [
{
label: '',
value: 'not set',
searchable: false
},
{
label: 'Notification',
value: 'notification',
searchable: true
}
];
// your custom filterOption function
filterOption = ({ label, value, data }, string) => {
if (string && data.searchable) {
return label.includes(string) || value.toString().includes(string);
} else {
return true;
}
};

MobX - Select single item in array, unselect all others?

I have the following observable array of search engines.
#observable favoriteSearchEngine = [
{ 'provider' : 'google', 'selected': true },
{ 'provider' : 'yahoo', 'selected': false },
{ 'provider' : 'bing', 'selected': false },
];
The user should only be able to select one at a time from the UI. So if they choose yahoo for example, yahoo would get selected: true and any other provider would get selected: false
This action handles the click:
#action onClickFavoriteSearchEngine = (provider) => {
alert(provider); // yahoo shows here
// How to do this step, only selected provider true and falsify all others in the array?
}
The solution given by #mweststrate works great, but since you are using an action (which also is a transaction), you could just unselect the previously selected, and select the new one if you would prefer:
#action onClickFavoriteSearchEngine = (provider) => {
alert(provider); // yahoo shows here
favoriteSearchEngine.forEach(e => e.selected = false);
favoriteSearchEngine.find(e => e.provider === provider).selected = true;
}
I would introduce a single observable representing selection, and derive the selected state from that:
#observable selection = null
#observable favoriteSearchEngine = [
{ 'provider' : 'google', 'selected': function() {
return selection === this
}
]
If you now assign another engine to the selection a few times, you will see that the selected state of the engines will update accordingly
(N.B. don't use arrow functions if declaring a plain object + derivation like this, to avoid issues with this)