Dynamically adding an array of objects to Vue (reactivity problem) - vue.js

I know that in order for an object or array to be reactive in Vue its properties have to be defined on the root data structure.
What's the best way to add an array of objects to a pre-existing variable defined on the root data structure, and make every property of every element in that array reactive?
I have tried looping through the array and adding each to the root data model, ie:
these_terms.forEach(function(term, idx) {
term.selected = false;
Vue.set(vm.game.set,idx,term);
});
However, Vue does still not respond to the "term.selected" property when it is later changed.
Is there a better way of achieving my aim, or do I need to resort to $forceUpdate? (the manual says that in 99% of cases using $forceUpdate, you're doing something wrong, hence this post)

On your parent component, do the following:
Make a data attribute with a empty array starting out
Make a button that calls a method
In that method, push to the empty array.
Example of step 3
methods: {
_addGroup: function() {
let result = {
id: this.wizardGroups.length + 1,
name: '',
};
this.wizardGroups.push(result);
},
If you need to append additional properties afterwards, you can loop through the array of objects and apply Vue.set() as well

Sorry if I understand it wrong but why dont you import the array and bring it into a Vue Data Variable?
import xx from "xxxx.js"
export default {
data() {
return {
y: xx
}
}
}

Related

Vuejs - add property to each item in an array with reactivity

I have banged my head against all kinds of walls for days and would love some help with this please.
I am getting the following error but can't really see how what I'm doing has anything to do with vuex here:
[vuex] do not mutate vuex store state outside mutation handlers.
In my vue component I define an empty array for future data I get from an external API.
data() {
return {
costCentres: [],
};
},
I have a watch on my vuex store object named schedule (which I've brought in using MapState... and also tried with a getter using MapGetter to see if that made any difference). In this watch I create my array costCentres, with each element consisting of about five properties from the API. I add two properties at this point (sections and tasks) which I intend to later populate, and which I need to be reactive so I do so in accordance with the Vue reactivity documentation which all the other questions I've found remotely like mine seem to reference.
watch: {
schedule() {
if (this.schedule.rows) {
this.costCentres = this.schedule.rows.filter((row) => {
return row.cells[
this.schedule.columnKeysByName["Cost Code"]
].value; // returns row if Cost Code value exists
});
this.costCentres.forEach((costCentre) => {
this.$set(costCentre, 'section', null);
this.$set(costCentre, 'task', null);
});
}
},
The this.$set lines throw the earlier mentioned error for every element in the array.
When I later update the properties, the change is reactive so its just the flood of error messages that's got me beat. Obviously if I don't use set then I don't get reactivity.
I have no idea how what I am doing is related to the vuex store as costCentre is a plain old data property.
I've tried hundreds of variations to get this all work (including this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 }) which doesn't seem to work) and I've run out of options so any assistance would be greatly appreciated!
(Please also let me know if I need to show more code - I was trying to keep this concise!)
this.schedule.rows is an array containing some objects (mapped from Vuex so the array and objects inside "belongs" to Vuex)
You are creating this.costCentres by filter - so in the end this.costCentres is just another array containing subset of objects from this.schedule.rows (elements inside are just pointers to objects inside Vuex)
In the forEach loop, you are modifying objects which are part of the Vuex store and as a result getting error [vuex] do not mutate vuex store state outside mutation handlers.
If you want to modify those objects, only way is to use Vuex mutations
Alternative solution is to make a copy of objects (create new objects with same values):
this.costCentres = this.schedule.rows.filter((row) => {
return row.cells[this.schedule.columnKeysByName["Cost Code"]].value;
})
.map((row) => ({...row, section: null, task: null }))
Note: code above creates just a shallow copy so if your objects does contain some deeply nested properties, you have to use some other way to clone them
Now objects inside this.costCentres are not part of the Vuex and can be modified freely without using mutations...

How to convert a Vue-wrapped object to a normal object

I need to assign the data variable of type object to a normal variable
const myVue = new Vue({
el: '#myVue',
data: {
vars: {}, // show Form
},
methods: {
assign_vars() {
const new_vars = this.vars;
},
},
});
html
<input type="text" v-model="vars.name" >
I need new_vars to be just like this:
new_vars: { name: 'test' }
but what happens is new_vars is having all the attributes that Vue creates
Vue wraps its observed data in a special object to efficiently observe changes (this is what enables reactivity). In most use cases you won't need to unwrap the object, but in case you do, here are a couple of methods:
const unwrapped = JSON.parse(JSON.stringify(this.vars));
This is the method suggested by Evan You here. It works well even for deeply nested objects, as long as the types can be converted to and from JSON (strings, numbers, booleans, objects, and arrays). If your data contains something else (e.g. dates), you'll need another approach.
const unwrapped = { ...this.vars }
This works well for shallow objects, even if they contain non-primitives.
If you have a deeply nested object containing non-primitive values, you'll need to write a function that recursively unwraps its sub-objects.
u can use toRaw(reactive object goes here) method.
If you want to keep functions, lodash.defaultsDeep({}, vm.$data) is a good idea.

Vue.js extend component and data updates

I'm using vue.js extends for the first time. I have a component that extends another and it needs to read the root components data to update the status in its own component.
What I'm finding is that the component that extends the other only seems to take a copy of the root's data when it's rendered but if I update a property in the root component it's not updated in the extended component.
So I might not be going about this the right way if the extended component doesn't update when the root does. For example I want to check the length of an array on the root component and update another data value. It updates the value on the root but not on the extended component.
Is this the expected behaviour and is there a way I can send the updated data down to the extended component?
Sample code:
<a inline-component>
<input type="text" v-model="myArray" />
<button v-on:click="saveData">Save</button>
</a>
<b inline-component>
<div v-if="myArray.length > 0">On Target</div>
</b>
var a = Vue.component('a', {
data: function () {
return {
myArray: [],
}
},
methods: {
saveData : function(){
var vm = this;
axios.post('/save', {
})
.then(function (response) {
vm.myArray = response.data;
})
.catch(function (error) {
console.log(error);
});
},
}
});
Vue.component('b', {
extends: a,
});
I have a component that extends another and it needs to read the root components data to update the status in its own component.
For the purposes of my answer I'm going to assume that you have two component definitions, A and B, and B extends A. I assume that when you say root you just mean A.
What I'm finding is that the component that extends the other only seems to take a copy of the root's data when it's rendered but if I update a property in the root component it's not updated in the extended component.
Rendering is not really relevant here. The data properties are set up when a component instance is created. Typically rendering will happen just after creation but merging any data happens much earlier in the component life-cycle. Even if the component isn't rendered the data will still be initialised.
No copying takes place. Let's consider a data function on component A:
data () {
return {
myArray: []
}
}
Every time this function is invoked it is going to return a new object, each containing a new array. This is precisely what happens if you create an instance of A directly. For each instance, Vue will call this function and get a new object defining the data. Generally that's what you'd want, rather that having components sharing data.
Now let's consider B. That might define its own data function. When an instance of B is created Vue will call the data function for both A and B and then merge the objects. No copying takes place, just merging. If you want to know more about how Vue handles merging in general see the documentation but for data the strategy is pretty simple. Properties from both objects will be combined with B taking precedence over A if there's a clash of property names. There is no recursive merging of properties.
So the idea of updating 'a property in the root component' is not particularly well-defined. You might be thinking of it as a bit like a prototype chain, where modifying a superclass would impact the subclass, but that isn't what's going on here. The data functions are invoked when the component is created and that's that. There isn't a lasting link back to the component definition like there is with a prototype chain.
If you really want all your component instances to share the same data value then it can be done, you just need to make sure that the data function is returning the same object/array every time. e.g.
const myArray = []
export default {
name: 'A',
data () {
return {
myArray
}
}
}
Written this way all instances of A will share the same array for myArray. So long as B doesn't define it's own value for myArray it will share it too.
For example I want to check the length of an array on the root component and update another data value. It updates the value on the root but not on the extended component.
I'm struggling a bit to understand what that means. It seems there are lots of assumptions about things being shared, single instances here. It's not entirely clear how you update the 'root' given it's a component definition and not a component instance.
If possible you should use a computed property for this. That would be inherited by B. Each instance of A (or B) would have their own value for this computed property, which might be a little wasteful if they're all going to be the same, but it's probably still the best way to go.
You could in theory use a watch. That should be inherited too but keep in mind it would be manipulating values for that particular instance.
Reading between the lines a little, if you wanted to update something on the 'root' so that it magically appeared in the subcomponents you could use the same shared reference-type trickery that I demonstrated earlier for myArray. You may need to be careful with how you update it though. If, for example, you used a watch you might find the you end up updating the same object many times, once for each instance of the component.
Update:
Based on the code you've posted it could be made to work something like this:
var myArray = [];
var a = Vue.component('a', {
data: function () {
return {
myArray: myArray // Note: using the same, shared array
}
},
methods: {
saveData : function(){
var vm = this;
axios.post('/save', {
})
.then(function (response) {
// Note: Updating the array, not replacing it
var myArray = vm.myArray;
myArray.splice(0, myArray.length);
myArray.push.apply(myArray, response.data);
})
.catch(function (error) {
console.log(error);
});
},
}
});
Vue.component('b', {
extends: a,
});
Your example didn't include any ES6 so I've refrained from using it but it would be a bit simpler if that were available.
The example above works by sharing the same array between all instances of the component and then mutating that instance. Assigning a new array to that property won't work as it would only update that particular component instance.
However, all that said, this is increasingly looking like a case where you should give up on trickery and just use the Vuex store instead.

Creating local copy of passed props in child component in vue.js?

In vue.js what is the right way to edit prop without changing parent data?
What I mean by that is whenever we pass any property from parent to child in vue.js then if we make any change to that property in child component then the change is also reflected in parent's component.
Is there any way in vue.js to make a local copy of passed property in a child?
I googled this but everywhere it is written that we can achieve this by doing this.
props:["user"],
data(){
return {
localUser: Object.assign({}, this.user)
}
}
here the user is passed an object and I am creating a copy of it in local user but it doesn't work at all, the local user is undefined.
Have you encountered a scenario like this where you have to make changes to a parent property in child component without affecting the state of parent component i.e- making your own copy in child and then edit it?
Any insights on this will be helpful.
I have also read somewhere that in In vue#2.3.3,when we want to pass a prop from Father to Child component, we need to manually create a local data to save the prop, that makes lots of useless works.
we can maually create the local data like this :
props: ['initialCounter'],
data: function () {
return { counter: this.initialCounter }
}
but this is not working in my case as well.
I am using vue cli 3.0.1 for the developemnt purpose.
Here is my code for the same.
In my application I have a list view.
When user clicks on the See Focused View button user is redirected to below mentioned view i.e is actaully a bootstrap - modal view.
Here user can edit the value of Name, but as I am passing name here as a property from aprent component so editing it here causes it to update on parent component as well i.e in the list view as well.
In your fiddle, the child component is using Object.assign() to create a copy of data, which is an array of objects. However, this only creates a shallow copy, so the array elements would still refer to the original instances, leading to the behavior you're seeing.
A few solutions to deep copy the array:
Use JSON.parse(JSON.stringify(this.data)), which works reasonably well for primitive object properties (String, Number, BigInt, Boolean, undefined, and null):
data() {
return {
local_data: JSON.parse(JSON.stringify(this.data))
}
}
(demo 1)
Map the objects into new ones, which works well if the depth is only 1 (nested arrays/objects will still be shallow copied):
data() {
return {
local_data: this.data.map(x => ({...x}))
}
}
(demo 2)
Use a utility library, such as lodash's cloneDeep:
data() {
return {
local_data: _.cloneDeep(this.data)
}
}
(demo 3)

Dynamically creating a reactive array in the Vuex's state

My component would like to add a new reactive-array field to the SST (vuex). I tried in beforeCreate hook, but the added array is not reactive; it's just a plain JS array.
Note that this is not the same as adding/removing elements from an existing array created at the Vue's initialization time. Such arrays are "wrapped" and become reactive as expected, mindful of "Array Change Detection" gotchas.
In my case, I'm trying to dynamically add an entirely new field of array type to the SST and make it reactive at the same time. Possible?
Have a look at Reactivity in Depth - Change Detection Caveats:
Change Detection Caveats
Due to the limitations of modern JavaScript, Vue cannot detect property
addition or deletion.
Since Vue performs the getter/setter conversion process during
instance initialization, a property must be present in the data object
in order for Vue to convert it and make it reactive.
But you say you are adding an array dynamically:
I'm trying to dynamically add an entirely new field of array type to the SST and make it reactive at the same time. Possible?
From the docs (bold is mine):
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value) method:
Vue.set(vm.someObject, 'myArrayName', [1,2,3]);
Which should help you making your array reactive.
I see two problems here:
add dynamically array using vuex.
add dynamically element to this array and render this element.
I've initiate array if not exist in add method because when I'm receiving data from server myArray is not exist.
My solutuion below:
myVuexArray.js
import Vue from 'vue'
const state = {
myObject: {
myArray: [],
}
}
const getters = {
getMyArray: state => {
return state.myObject.myArray;
}
}
const mutations = {
addElementToArray(state, value) {
if (state.myObject.myArray === null || state.myObject.myArray === undefined || state.myObject.myArray === '') {
// initiate array
state.myObject.myArray = [];
}
// add new element to array
Vue.set(
state.myObject.myArray,
state.myObject.myArray.length,
value
);
// creates a new array everytime this solves the reactivity issue
Vue.set(state, 'myObject.myArray', state.myObject.myArray);
return state.myObject.myArray;
},
removeElementFromArray(state, index) {
state.myObject.myArray.splice(index, 1);
}
}
export default {
state,
mutations,
getters
}
Best regards
Dynamic module registration could help you to achieve this :
https://vuex.vuejs.org/en/modules.html
This would allow you to dynamically register a new module containing your array field in the beforeCreate hook.