VueJS - Update a parent computed property after a child component modifies prop data - vue.js

I have a relatively complex API request object I need to make, with a large number of UI components responsible for updating different properties of the object.
I'm passing the basic request model as a prop from a parent component to its children, which pass it on to theirs (down several "generations").
At the parent level, I have a computed property that returns a field of this data model, and a watch on that computed property.
When a child component updates the property on the model, it successfully updates everywhere that has a reference to it, but the computed property on the parent fails to recalculate, and resultantly the watch never activates.
I'm guessing I've missed the point somewhere along here, but I can't think about how else to update without resorting to long event chains through the UI.. How should I be approaching this instead?

To anyone with a similar question - from my research it seems that modifying reference values on props is not the intended approach for VueJS. Which is a shame, because initially it seemed like quite a neat pattern.
I've implemented vuex now, which is working well, and avoids long lines of events going back to the original owner of the prop data.
IF you wanted to press it, then modifying references on the object itself will force updates down the chain. So (e.g.) if you wanted to update an array property of the prop data, then instead of "pushing" to it, you would replace the whole array object (causing other components with computed properties on that array property to recalculate). But again, not recommended.

Related

Vue.js list rendering diff: "same object" vs "has same values"

I have just hit a reactivity problem in Vue.js (2.6.12) component. It was not re-computing and re-rendering after its props were updated. The issue is that the component is used in a list with v-for with a key. The key was not changing, but the object's data was changing i.e. it was the same object, but its data was different.
The key is used as an optimization for diffing the list in v-for so it can re-use and re-order the same components without re-creating them. My original key was the object's id and the solution is to create the key from the id and all the object's changing data. That leads to other issues, like losing the component's internal data state, which has no need to be stored in a Vuex store.
My question is, is there a way in Vue.js (maybe in v3?) to tell in v-for not only if the object is the same (via key using object's id) but also if the object has the same data? Which will update the prop and re-computed and re-render the component without losing its internal data?
Funny thing is that the component's props are updating, it just does not re-compute and re-render. Verified via the Vue Devtools in a browser.
EDIT:
Here is an example where the reactivity does not work. It is using a Vuex store and the array for v-for is a computed property, which joins two properties from the store.
I just figured out that the problem is the joining of the values via the map on line 67: o.number = extras[o.id]. After replacing this with Vue.set(o, 'number', extras[o.id]) it is working!
This problem doesn't exist as described. Here is a small example of object reactivity working properly: https://jsfiddle.net/sh0ber/04s1jt8g
I'm guessing, however, that you're encountering the change detection caveat and need to use Vue.set.
The reason is now clear with the code example: Your computed creates a new property (number) on each Vuex obj that's not defined in Vuex at the time you set objs. If you had initialized the objs to have a number property as well, you wouldn't need Vue.set. Another way to prove it is to set o.text instead of o.number and you'll see it's reactive without Vue.set
(Btw, it makes more sense to do this computation in a Vuex getter instead of a computed; getters are computeds for Vuex. It's nice to decouple the logic from the component, and imagine you needed the result in multiple components)

Should I create the deep copy of Vue component parameter?

From Vue documentation:
All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around.
However, if to pass the nested object or array from parent's data as child component's parameter and child will change it, the data of parent will change, too.
Ideally, good framework must take care about deep copying when required, but Vue does not.
The one of solution is creating the copy based on parameter's value. Should I do it?
I dont think creating copy is a good solution as far as i know.
The best practice for your task is to use .sync modifier!

vuejs computed properties - when update triggered?

It appears that a vuejs computed property will only be triggered to update if it is actually bound in the template or view. Is this correct?
The reason I ask is because I'm trying to use a computed as a watcher for a prop that is passed to my component, but I don't actually want to display anything from the computed. I just want the computed to update another data item when the prop changes. If the computed requires a binding to actually work, I think I'm better off using a watch in this case.
Computed properties are evaluated lazily, so the function you provide won't be called until something tries to read the value of the property.
If dependencies change then the value is marked as dirty but it won't be recalculated immediately. Again, that only happens if the value is accessed.
You can force the calculation of a computed property just by reading its value. Adding a watch for it will also ensure that it is always evaluated as the watcher needs to know as soon as the value changes.
Using a watch instead of a computed property might be a better way to go in the scenario you've described but it can be very convenient to abuse a computed property to get the automatic dependency tracking. It looks like this will be exposed more directly in Vue 3 so that such workarounds aren't required.

Reacting to changes within observed object's unobserved properties

I have an external library with TypeScript class instances in observed variable of my app's store:
class Store:
#observable active_obj:ExternalClass;
This instance has a method which, when called, updates its internal state leaving the object as is:
store.active_obj.change()
Now, since Mobx is not observing this object itself nor its (I assume) properties, only changes happening directly on the field active_obj (like new value assigned to it). Since instances of MyClass are provided by external library and ultimately get rendered inside this library's components I can't add observables and observers to its class structure nor React components. Mind you, this is only what I think is the reason that changing the object's properties doesn't trigger re-render...
I had to cheat a bit by using other, observed variable I change directly with nonsense data at the same time I'm calling to unobserved instance for change. Adding references to this variable on components higher up the tree, I can trigger re-render that produces updates on the (unobserving) components of the external library.
My question is, how best to make Mobx aware of change so it can notify observers of store.active_obj instance?
I think this part of Mobx documentation warns about this, but there's no workarounds or solutions for it:
** If likes where objects instead of strings, and if they were rendered by their own Like component, the Likes component would not rerender for changes happening inside a specific like.
from here, at end of the page
Edit
As per #mweststrate's question, I provide some context:
My app controls its data, but it has to create external class' instances from that
Instance's data is encapsulated and mutated in place, but it's done by asking from my app's side through user triggered events (meaning, I know when data is updated)
Basically class uses app's data to provide different views into data based on user selection and renders it with its React components
I also use this changed data elsewhere in the app in components I control
Changed data is part of external class' internals and I can't depend on it
Since Mobx tracks mutations it can see, using Observable doesn't directly work
Some possible solutions I thought:
manually notify observers that observable active_object has changed when I have called the instance it references to change
have a container object that Mobx can track and when I change its sentinel property, that update is noticed and actual instance with it

Children to children communication

My component is pretty big so I will give you simplified example, maybe someone will know how to solve my problem.
I have invoice component with children components like 'subtotal', 'vat_subtotal', 'total' (it's example). I'm using v-ref to get direct access to every child from invoice component. Also, subtotal is calculated from invoice properties, then vat_subtotal is calculated from subtotal children properties. And 'total' is calculated from vat_subtotal children.
Example:
invoice.$refs.subtotal.total = {some calculations}
vat_subtotal.total = #$parent.$refs.subtotal.total * 1.21
total.total = #$parent.$refs.vat_subtotal.total
The problem is that i'm getting warnings when page loads, be cause 'total' children is trying to access 'vat_total' children properties, but #$parent.$refs.vat_total is still 'undefined' (I don't know why. When later im changing something in form, it reacts normaly and recalculate everything right). It seems, that one children is trying to compute properties while other children isn't loaded yet.
Don't reach into a child for data. If the parent needs access (in your case, to give access to another child), the data item should be created in the parent and passed as a prop to the child. That is how data can be shared among multiple children. Child components should not depend on other child components being available through the parent (or on anything in the parent, really, if it isn't passed in as a prop).
If the child component is responsible for changing the prop value, you can pass it using .sync.
The way you are trying to solve things is technically possible but highly discouraged. As stated in the docs:
Although it’s possible to access any instance in the parent chain, you should avoid directly relying on parent data in a child component and prefer passing data down explicitly using props. In addition, it is a very bad idea to mutate parent state from a child component, because:
It makes the parent and child tightly coupled;
It makes the parent state much harder to reason about when looking at it alone, because its state may be modified by any child! Ideally, only a component itself should be allowed to modify its own state.
In general, you should aim for creating a model representing the state of your application. Instead of accessing parents / children directly, pass any relevant data down to children using props. Children can notify their parent about changes using events, or you can use .sync binding to synchronize models automatically.
I think that you would benefit from reading about more structured approach to Vue application architecture. I'd start with Flux-inspired Application Architecture for Vue.js.