Vue Router and VueJS reactivity - javascript

I've got this code for my vue-router:
{
path: '/templates/:id/items/:itemId', component: Item,
name: 'item'
},
On the item object, I've got a computed property templateId:
templateId() {
return parseInt(this.$route.params.id, 10);
},
The issue I have, is, each time I add an anchor to the url (clicking a link, INSIDE the item component), even if the component doesn't change, this property is computed again.
It means that all computed properties depending of templateId will be computed again.
But the templateId value doesn't change at all.
Here is a really simple jsfiddle to explain the issue:
https://jsfiddle.net/1Lgfn9qh/1/
If I remove the watch property (never called), nothing is logged in the console anymore.
Can you explain me what's happening here?
Why the computed properties are recomputed, even if no values has been updated?
How can I avoid that?

What's causing this behaviour is the fact, that the route object is immutable in Vue. Any successful navigation will result in a completely new route object therefore triggering a re-computation of all computed and watched properties. See https://router.vuejs.org/api/#the-route-object for more details.
To solve this you can watch the route object and filter the relevant vs irrelevant changes for you there
watch: {
'$route' (to, from) {
if(to.path != from.path) { // <- you may need to change this according to your needs!
this.relevantRoute = to
}
}
}
And then reference the manually set variable in your computed and/or watched properties
templateId() {
return parseInt(this.relevantRoute.params.id, 10);
},

Related

Storing props locally (vue2)

Following:
Passing data from Props to data in vue.js
I have:
https://codesandbox.io/s/u3mr8
which gives the following warning:
(the idea is to avoid mutating props). What side effects can happen in a straightforward copy object operation? I don't get it. The function just saves props into data.
Drag and drop fails with:
Do you really need a setter for a computed prop?
Looking at:
Computed property was assigned to but it has no setter - a toggle component
I've come up with:
https://codesandbox.io/s/39sgo
which is great, no warnings, no errors; it's just that the component no longer renders (fails to get data saved from prop).
Any ideas/suggestions/help/advice would be really, really awesome.
I think the error is thrown because it is not allowed to set within the getter the value from which the computed property is generated. It is a logical loop to modify the initial value while getting the computed results. Instead you can just return the prop value on initial call to getter (if the local value is not yet set).
get() {
if (!this.itemSectionPropsLocal["itemSectionCategory"]) {
return Object.assign({}, this.itemSectionProps)[
"itemSectionCategory"
];
}
return this.itemSectionPropsLocal["itemSectionCategory"];
},
set(value) {
this.itemSectionPropsLocal = value;
},
Also, in setter, you should assign the received value not the prop. If you want to update the local values if the prop value changes after mount you should use a watcher.
watch: {
itemSectionProps: {
deep: true,
handler(val){
this.getPropsLocal = Object.assign({}, val["itemSectionCategory"])
}
}
}

Vue Reactivity: Why replacing an object's property (array) does not trigger update

I have a Vue app where I'm trying to make a thin wrapper over the Mapbox API. I have a component which has some simple geojson data, and when that data is updated I want to call a render function on the map to update the map with that new data. A Vue watcher should be able to accomplish this. However, my watcher isn't called when the data changes and I suspect that this is one of the cases that vue reactivity can't catch. I'm aware that I can easily fix this problem using this.$set, but I'm curious as to why this isn't a reactive update, even though according to my understanding of the rules it should be. Here's the relevant data model:
data() {
return{
activeDestinationData: {
type: "FeatureCollection",
features: []
}
}
}
Then I have a watcher:
watch: {
activeDestinationData(newValue) {
console.log("Active destination updated");
if (this.map && this.map.getSource("activeDestinations")) {
this.map.getSource("activeDestinations").setData(newValue);
}
},
}
Finally, down in my app logic, I update the features on the activeDestination by completely reassigning the array to a new array with one item:
// Feature is a previously declared single feature
this.activeDestinationData.features = [feature];
For some reason the watcher is never called. I read about some of the reactivity "gotchas" here but neither of the two cases apply here:
Vue cannot detect the following changes to an array:
When you directly set an item with the index, e.g. vm.items[indexOfItem] = newValue
When you modify the length of the array, e.g. vm.items.length = newLength
What am I missing here that's causing the reactivity to not occur? And is my only option for intended behavior this.set() or is there a more elegant solution?
as default vue will do a shallow compare, and since you are mutating the array rather than replacing, its reference value is the same. you need to pass a new array reference when updating its content, or pass the option deep: true to look into nested values changes as:
watch: {
activeDestinationData: {
handler(newValue) {
console.log("Active destination updated");
if (this.map && this.map.getSource("activeDestinations")) {
this.map.getSource("activeDestinations").setData(newValue);
}
},
deep: true
}
}
If you need to watch a deep structure, you must write some params
watch: {
activeDestinationData: {
deep: true,
handler() { /* code... */ }
}
You can read more there -> https://v2.vuejs.org/v2/api/#watch
I hope I helped you :)

How can I make an Ember computed property depend on all descendent properties of a variable?

I'm trying to create a computed property that I want to be reevaluated whenever any value in a deeply nested object changes. I understand that myObj.[] can be used to reevaluate computed properties whenever any object in an array changes, but I want this to be recursive.
eg I have
// should recalculate whenever myObj.x.y.z changes, or when myObj.a.b.c changes
computed('myObj', function() {
// ...
})
I don't know in advance exactly how the object is structured, and it may be arbitrarily deep.
Neither computed('myObj.[]', ...) nor computed('myObj.#each', ...) seem to work for this.
Any ideas how to do this?
In Ember it is possible to define computed properties at runtime
import { defineProperty, computed } from '#ember/object';
// define a computed property
defineProperty(myObj, 'nameOfComputed', computed('firstName', 'lastName', function() {
return this.firstName+' '+this.lastName;
}));
So taking that a step further, you could dynamically create whatever computed property key string you want at runtime (this could be in component's init() or something):
// define a computed property
let object = {
foo: 'foo',
bar: 'bar'
}
this.set('myObj', object);
let keys = Object.keys(object).map((key) => {
return `myObj.${key}`
});
defineProperty(this, 'someComputed', computed.apply(this, [...keys, function() {
// do something here
}]));
It's up to you to figure out how to properly recursively traverse your objects for all the dependent keys without creating cycles or accessing prototype keys you don't want...or to consider whether or not this is even that good of an idea. Alternatively, you could try to handle the setting of these properties in such a way that retriggers a computation (which would be more in line with DDAU). I can only speculate from what you've provided what works but it's certainly possible to do what you want. See this twiddle in action
could you try anyone computed/obeserver like below..
But try to prefer the computed.
import { observer } from '#ember/object';
import EmberObject, { computed } from '#ember/object';
partOfNameChanged1: observer('myObj','myObj.[]','myObj.#each', function() {
return 'myObj is changed by obeserver';
})
partOfNameChanged2: computed ('myObj','myObj.[]','myObj.#each', function() {
return 'myObj is changed by computed';
})
then in your handlebar/template file
{{log 'partOfNameChanged1 is occured' partOfNameChanged1}}
{{log 'partOfNameChanged2 is occured' partOfNameChanged2}}
Then you have to associate/assign this partOfNameChanged1 / partOfNameChanged2 to some where in the handlebar or to any other variable in your .js file.
As long as you have not assigned this computed/observer property partOfNameChanged1 /partOfNameChanged2 to somewhere, then you will not get it's value.

Child prop not updating, even though the parent does

I have a pretty weird situation where a change in a child component's prop is not triggering a re-render.
Here is my setup. In the parent I have:
<child :problemProp="proplemPropValue""></child>
In the child I have defined the prop:
{
name: "Child",
props: {
problemProp: {
type: [File], //yes it is a file
required: true
}
}
and then I try to render it (still in the child component)
<template>
<div id="dropzone-preview" class="file-row">
{{problemProp}} <--This should just show the prop as a JSON string-->
</div>
</template>
This is rendered correctly initially. But problemProp has a property upload.progress, and when I change it in the parent (I can confirm that it does change on the parent) it does NOT change in the child.
If I now add a second prop, dummyProp to the child:
{
name: "Child",
props: {
problemProp: {
type: [File], //yes it is a file
required: true
},
dummyProp: {
type: Number
}
}
Now, when dummyProp changes, propblemProp also changes. What is going on here? Why does the change in dummyProp force a re-render but a change in propblemProp does not?
The root of the problem appears to be that when you add a File object to the array, Vue fails to convert it into an observed value. This is because Vue ignores browser API objects:
The object must be plain: native objects such as browser API objects
and prototype properties are ignored.
That being the case, any changes to that object will not be reflected in the Vue automatically (as you would typically expect) because Vue doesn't know they changed. That is also why it appears to work when you update dummyProp, because changes to that property are observed and trigger a re-render.
So, what to do. I've been able to get your bin to work by making a small change to the addedFile method.
addedfile(file){
console.log(file);
self.problemPropValues.push(Object.assign({}, file));
},
First, you don't need (or want) to use $data, just reference the property directly. Second, by making a copy using Object.assign, Vue properly observes the object and updates are now reflected in the child. It may be the case that you will need to use a deep copy method instead of Object.assign at some point, depending on your use case, but for now this appears to be working.
Here is the code I ended up with getting the bin to work (converted to codepen because I find working with codepen easier).

Reset Ember Component on Load

I have an Ember.Component that adds items to an empty array and returns the array on submission. The problem is, if I navigate away from the Route that contains the Component (both after submitting and without submitting), and then go back to it later, the information that was last in the array is still there. I would like to to be reset every time I navigate to the route with the component.
If this were a route, I'd simply write a willTransition or deactivate method to reset my attributes. But since it's a component, it doesn't have those methods, and I can't (that I know of) access the attribute I wish to reset from the parent route. So, how can I reset this array to be empty (or reset the the entire component) every time I load this route? Thanks!
More likely than not, you're not setting the value you're using properly. Take these examples:
Ember.Component.extend({
items: []
});
Ember.Component.extend({
items: null,
init: function() {
this._super();
this.set('items', []);
}
});
In the first component, the same items array is shared by every instance of the component. So if you add an item, then create a new component, the new component still has the item (which I think is your problem).
In the second component, you can see that I set the items property in the init function. And when I set the property, I set it to a different array every time. Now, each component has their own items property.
It's hard to say without your code, but this seems like your issue.

Categories

Resources