Watch for variable change do not work - vue.js

I need to check variable rasters_previews_list for changing. Here is my code:
var userContent = Vue.extend({
template: '<p>Some template</p>',
data: function () {
return {
rasters_previews_list: []
}
},
watch: {
'rasters_previews_list': function(value, mutation) {
console.log("Value changed");
}
}
});
But In console I do not see Value changed when it got new data.
Data changing function:
map.on('draw:created', function (e) {
//...
Vue.http.post('/dbdata', DataBody).then((response) => {
userContent.rasters_previews_list = response; // putting JSON answer to Component data in userContent
console.log(response);
}, (response) => {
console.log("Can't get list rasters metadata from DB. Server error: ", response.status)
});
I change value in map.on('draw:created', function (e) (Leaflet JS). I see console.log output, so seems data is changing.

If you want to change the value of an array you will have to use the special Array extension methods Vue.set and Vue.delete.
Due to limitations of JavaScript, Vue cannot detect the following changes to an Array:
When you directly set an item with the index, e.g. vm.items[0] = {};
When you modify the length of the Array, e.g. vm.items.length = 0.
https://vuejs.org/api/#Vue-set
This problem is also mentioned in the common gotchas
When you modify an Array by directly setting an index (e.g. arr[0] = val) or modifying its length property. Similarly, Vue.js cannot pickup these changes. Always modify arrays by using an Array instance method, or replacing it entirely. Vue provides a convenience method arr.$set(index, value) which is just syntax sugar for arr.splice(index, 1, value).

Related

Computed Property causes array and object mutation

I am trying to pass data to a v-select dropdown.
This of course works:
computed: {
itemDropdown() {
const menuItems = {
id: "1",
name: "Joe"
}
return menuItems;
}
}
But when I try:
computed: {
itemDropdown() {
const newArray = [...this.data.originalItems];
newArray.map(item => {
item.name = "myCoolNewName";
});
return newArray;
}
}
It mutates the original array.
I have also tried copying the object:
computed: {
itemDropdown() {
const newObj = { ...this.data };
newObj.items.map(item => {
item.name = "myCoolNewName";
});
return newObj;
}
}
Not sure what I’m missing, but wondering if there is a work around. Thanks for any help :slight_smile:
You are using the map array method wrong.
The first thing you need to know, is that the map method returns a new array, so you have to either return the result of your map function or save it in a variable, otherwise you will just be looping through your array without ever saving it anywhere.
Another thing is about how you use the map method.
Here I have made an example of how it should work with your code:
computed: {
itemDropdown() {
return this.data.originalItems.map(item => {
return {
name: "myCoolNewName"
}
});
}
}
The big difference you should notice, is that inside the map function, we have to return what we want each object to look like, after we have gone through it. We want it to give us the object back, but make some changes to it, so we have to actually return an object and change what we want in that.
What you were doing before, was refering to the item in the old array, and assigning it a new value, instead of returning a new object with your changes.
You can read about the array.map method here
Hope that makes sense :)

How do I watch changes to an object? this.$set seems to capture only property update

I have this object of arrays that I'm tryin to watch every update of.
myData = {
"299":[527],
"376":[630,629]
}
I read this documentation on watching an object which instructed to use either this.$set(object, propertyName, value) or Object.assign({}, this.object, dataToBeAppended) to watch an object. I used this.$set.
export default {
...
data() {
return {
myData: {},
};
},
watch: {
myData(newVal) {
console.log(`🔴localStorage`);
},
},
methods: {
onFoldChange(propertyName) {
const newArr = [...]
this.$set(this.myData, propertyName, newArr);
},
}
}
Unlike what I expected, vue captures changes on property only. Changes in value to an existing property are not being watched. For example, if a property "299" was newly added, it will print 🔴localStorage. When the value of a property "299" is updated from [527] to something else, nothing is fired. When I print myData, I see every value updated correctly. It is just that watch isn't capturing the changes.
The documentation also described we can watch an array using this.$set(this.myData, indexOfItem, newValue) so I also tried array version of the above code, like this.
this.$set(this.myData[propertyName], index, newValueToAdd);
This time it doesn't listen at all. Not even the first entry.
Is there any better way to solve this issue? How do others watch an object? Is the complication coming from the type of values (array) ?
Currently, myData watcher observes only an object. Object contains pointers to arrays as in JS Objects & Arrays are passed by reference not by copy. That's why it can detect only changes in keys and with simple values. If you want to observe it deeper - I mean also those subarrays (or subobjects) - just use deep watch.
watch: {
myData: {
deep: true,
handler (newVal) {
console.log(`🔴localStorage`);
}
}
}
Another possible solution could be to use some Array.prototype operation to modify an array if it already exists. E.g:
methods: {
onFoldChange(propertyName) {
if (propertyName in this.myData && Array.isArray(this.myData[propertyName])) {
this.myData[properyName].push(162) // Some random value
} else {
const newArr = [...]
this.$set(this.myData, propertyName, newArr);
}
},
}

Can't get data of computed state from store - Vue

I'm learning Vue and have been struggling to get the data from a computed property. I am retrieving comments from the store and them processing through a function called chunkify() however I'm getting the following error.
Despite the comments being computed correctly.
What am I doing wrong here? Any help would be greatly appreciated.
Home.vue
export default {
name: 'Home',
computed: {
comments() {
return this.$store.state.comments
},
},
methods: {
init() {
const comments = this.chunkify(this.comments, 3);
comments[0] = this.chunkify(comments[0], 3);
comments[1] = this.chunkify(comments[1], 3);
comments[2] = this.chunkify(comments[2], 3);
console.log(comments)
},
chunkify(a, n) {
if (n < 2)
return [a];
const len = a.length;
const out = [];
let i = 0;
let size;
if (len % n === 0) {
size = Math.floor(len / n);
while (i < len) {
out.push(a.slice(i, i += size));
}
} else {
while (i < len) {
size = Math.ceil((len - i) / n--);
out.push(a.slice(i, i += size));
}
}
return out;
},
},
mounted() {
this.init()
}
}
Like I wrote in the comments, the OPs problem is that he's accessing a store property that is not available (probably waiting on an AJAX request to come in) when the component is mounted.
Instead of eagerly assuming the data is present when the component is mounted, I suggested that the store property be watched and this.init() called when the propery is loaded.
However, I think this may not be the right approach, since the watch method will be called every time the property changes, which is not semantic for the case of doing prep work on data. I can suggest two solutions that I think are more elegant.
1. Trigger an event when the data is loaded
It's easy to set up a global messaging bus in Vue (see, for example, this post).
Assuming that the property is being loaded in a Vuex action,the flow would be similar to:
{
...
actions: {
async comments() {
try {
await loadComments()
EventBus.trigger("comments:load:success")
} catch (e) {
EventBus.trigger("comments:load:error", e)
}
}
}
...
}
You can gripe a bit about reactivity and events going agains the reactive philosophy. But this may be an example of a case where events are just more semantic.
2. The reactive approach
I try to keep computation outside of my views. Instead of defining chunkify inside your component, you can instead tie that in to your store.
So, say that I have a JavaScrip module called store that exports the Vuex store. I would define chunkify as a named function in that module
function chunkify (a, n) {
...
}
(This can be defined at the bottom of the JS module, for readability, thanks to function hoisting.)
Then, in your store definition,
const store = new Vuex.Store({
state: { ... },
...
getters: {
chunkedComments (state) {
return function (chunks) {
if (state.comments)
return chunkify(state.comments, chunks);
return state.comments
}
}
}
...
})
In your component, the computed prop would now be
computed: {
comments() {
return this.$store.getters.chunkedComments(3);
},
}
Then the update cascase will flow from the getter, which will update when comments are retrieved, which will update the component's computed prop, which will update the ui.
Use getters, merge chuckify and init function inside the getter.And for computed comment function will return this.$store.getters.YOURFUNC (merge of chuckify and init function). do not add anything inside mounted.

Declaring variable in "data" section as an alias to $root is not reactive

I declare variable in main.js:
data: {
globalData: {}
}
I want to avoid using this.$root.globalData all the time — so I use local variable in a component as an alias to "global variable":
data() {
return {
localAlias: this.$root.globalData,
}
}
Then I fetch global variable from a server in main.js (simulate by setTimeout):
create() {
window.setTimeout(() => {
this.globalData = {a:1, b:2};
}, 1500);
}
And localAlias remains equal to initial value.
How to make it work? I don't need Vuex yet, I just grab data from server and use it read-only.
Example
Instead of using data you can use computed. It will solve your problem.
computed: {
localAlias: function() {
return this.$root.globalData;
}
}
I have updated the example
The reason localAlias doesn't change is because it still points to the same object, while you re-point this.$root.globalData to a new object. One way to do it is of course to use computed as the other answer suggested. Another way to solve it it to just change the properties instead of re-binding the entire object:
create() {
window.setTimeout(() => {
this.globalData.a = 1;
this.globalData.b = 2;
}, 1500);
}
This is less versatile though and will scale worse if the object becomes bigger.

Watch all properties of a reactive data in Vue.js

I had an API call to the backend and based on the returned data, I set the reactive data dynamically:
let data = {
quantity: [],
tickets: []
}
api.default.fetch()
.then(function (tickets) {
data.tickets = tickets
tickets.forEach(ticket => {
data.quantity[ticket.id] = 0
})
})
Based on this flow, how can I set watcher for all reactive elements in quantity array dynamically as well?
You can create a computed property, where you can stringify the quantity array, and then set a watcher on this computed property. Code will look something like following:
computed: {
quantityString: function () {
return JSON.stringify(this.quantity)
}
}
watch: {
// whenever question changes, this function will run
quantityString: function (newQuantity) {
var newQuantity = JSON.parse(newQuantity)
//Your relevant code
}
}
Using the [] operator to change a value in an array won't let vue detect the change, use splice instead.