vueJS- prop is being mutated when local copy updates - vue.js

My child component template has two input elements with v-models as below.
<input v-model="user.name" />
<input v-model="user.email" />
props: {
'account': {}
},
data: function() {
return { user : this.account.user }
}
When ever the text in the input fields changes, The user object is being updated which is expected. But the prop account.user is also updating with the changes. How can i make it to update just the user object and keep the prop account.user as it is?

This happens because in Javascript, object is passed by reference. When you do user : this.account.user, you are passing the object reference to user data. That's why when you edit user data, account.user gets edited as well, they refer to the same object.
You can clone it using ES6 spread operator.
return { user : {...this.account.user} }
If you are not using ES6, i would suggest you to use lodash clone instead.
return { user : _.clone(this.account.user) }
(Btw, the cloning methods above only works for shallow object. For deeply nested object, use lodash cloneDeep instead.)

Related

vue unexpected reactivity from props

I just noticed an unexpected behaviour and now I don't know if it is normal or not.
I have a component named follows and a child component named follow-list-modal
I'm passing a followList (pagination ) from follows to its child component follow-list-modal
In the follow-list-modal I store the paginated array in the variable members
Follows.vue
<template>
<div>
<follow-list-modal
:follow-list="dataset">
</follow-list-modal>
</div>
</template>
<script>
export default {
props: {
dataset: {
type: Object,
default: {},
},
},
}
</script>
FollowListModal.vue
<template>
<div>
<button #click="fetchMore"> More </button>
</div>
</template>
<script>
export default {
props: {
followList: {
type: Object,
default: {},
},
data() {
return {
members: this.followList.data,
dataset: this.followList,
};
},
methods: {
fetchMore() {
let nextPage = parseInt(this.dataset.current_page) + 1;
axios
.get(this.dataset.path + '?page=' + nextPage)
.then(({ data }) => this.refresh(data))
.catch((error) => console.log(error));
}
},
refresh(paginatedCollection) {
this.dataset = paginatedCollection;
this.members = this.members.concat(...paginatedCollection.data);
},
}
When I click the button More in the follow-list-modal to get more data, I then want to append the new data to the members array.
The unexpected behaviour ( for me at least ). is that if I use push in the refresh method
this.members.push(..paginatedCollection.data);
It appends data not only to members but also to followList which is data that comes from the parent component follows
But if I use concat instead of push, it appends data only to members variable and not to followList
this.members = this.members.concat(..paginatedCollection.data);
Is this behaviour normal ?
I don't get why the followList changes when the members variable changes, I thought that reactivity is one way.
In other words, the members changes when the followList changes, but not the other way around
P.S I don't emit any events from follow-list-modal to follows or change the data of the follows component in any way from the follow-list-modal
In JavaScript, the properties of an Object that are also Objects themselves, are passed by reference, and not by value. Or you might say that they are shallow copied.
Thus, in your example, this.members and this.followList.data are pointing to the same variable in memory.
So, if you mutate this.members, it will mutate this.followList.data as well.
You could avoid this by doing a deep copy of the objects. The easiest method, and arguably the fastest, would be to use JSON.parse(JSON.stringify(obj)), but look at this answer for more examples.
data() {
return {
members: [],
dataset: [],
};
},
created() {
this.members = JSON.parse(JSON.stringify(this.followList.data));
this.dataset = JSON.parse(JSON.stringify(this.followList));
}
You instantiate your data with a direct link to the (initially undefined) property of your prop. This property is a complex entity like an Object (Arrays are Objects), and is thus called via reference. Since members references the same thing in memory as followList.data, when you're calling members, it will follow the reference to the same entity as followList.data. This doesn't have to do with Vue2 reactivity, but here's a link nontheless.
push mutates the array it is called on; it will follow the reference through members and change followList.data, updating its value when called through followList as well. Because the data key is not present on instantiation of the component, Vue can't watch it (just like you need to use Vue.set when adding a new key to a data object).
concat returns a new array of merged elements, and then replaces
the reference in members with the new array. Therefore from this point on you'll
no longer mutate followList.data, even with a push, as the reference has changed to a new entity.
When trying to set your initial members and dataset, I suggest using an initialization method that creates a clone of your followList and writes that to dataset, and running this on the created() or mounted() hook of your component lifecycle. Then create a computed property for members, no need to store followList.data thrice and potentially have dataset and members diverge.

How to bind a dynamic Object with v-bind in vue

I would like to ask a question of v-bind dynamic object. When binding is a completely dynamic object, how to bind properties with operation expression? How do I keep the attr observable? thank you!
<template>
<component v-bind="dynamicAttrs"></component>
</template>
export default {
data(){
return {
someObject:{
a:{
b:10
}
},
// This dynamicAttrs object is dynamically defined and save into the mysql database by the secondary development user,
// will load it from mysql for component init.
// All the properties of this object are dynamic, that is, how many properties are there, and the name of the property is dynamic.
dynamicAttrs:{
someAttr1:123,
someAttr2:"someObject.a.b + 500" // How does this object with operation expressions bind? How do I ensure that it gets updated responsively, so that component gets the update automatically when `someobject.a.b` updated
}
}
}
}
The dynamicAttrs object is dynamically defined and save into the mysql database by the secondary development user, load it from mysql for component init.
How does this object with operation expressions bind? How do I ensure that it gets updated responsively, so that component gets the update automatically when someobject.a.b updated
Typically you'd use a computed property for this:
computed: {
dynamicAttrs () {
return {
someAttr1: 123,
someAttr2: this.someObject.a.b + 500
}
}
}
If someObject.a.b updates the computed property will be re-evaluated and the bindings will be updated.

How to reset form elements with VueJS and Vuex

I have a "Question and Answer" component written in VueJs, with a Vuex store. Each answer is a <textarea> element, such as the following:
<textarea class="form-control" rows="1" data-answer="1" :value="answer(1)" #change="storeChange"></textarea>
As you can see the value of the control is set by calling an answer() method and passing the question number as a parameter.
When the answer is changed the storeChange method is called and the changes are cached in a temporary object (this.changes) per the following code:
props : [
'questionnaire'
],
methods : {
answer(number) {
if (this.questionnaire.question_responses &&
(number in this.questionnaire.question_responses)) {
return this.questionnaire.question_responses[number];
}
return null;
},
storeChange(e) {
Vue.set(this.changes, e.target.dataset.answer, e.target.value);
},
save() {
// removed for clarity
},
reset() {
// what to do here?
},
}
If the user clicks the save button I dispatch an action to update the store.
If the user wants to reset the form to its original state, I need to clear this.changes, which is no problem, but I also need to 'refresh' the values from the store. How do I do this?
Note that the source of the initial state, questionnaire, comes via a prop, not a computed property that maps directly to the store. The reason for this is that there can be multiple "Question and Answer" components on one page, and I found it easier to pass the state this way.
we can by using refs reset form , example
form textarea
<form ref="textareaform">
<textarea
class="form-control"
rows="1"
data-answer="1"
:value="answer(1)"
#change="storeChange"
>
</textarea>
<button #click="reset">reset</button>
</form>
reset
reset() {
// ref='textareaform'
// reset() method resets the values of all elements in a form
// document.getElementById("form").reset();
this.$refs.textareaform.reset()
},

How know if Object passed by prop is changed in Vuejs

How can i know if my object retrivied by props is changed or not?
Example.
I have an object passed by props like:
object:{
id: 1,
list: [{..},{..}],
propertyExample: true,
message: "I know that You will change this input"
}
And in my html frontend I have an input that change value of message or another property like:
<input type="text" v-model="object.message" />
And I would notify when my "entire original object" (that passed by prop) is changed. If I use watch deep the problem As documentation says is:
Note: when mutating (rather than replacing) an Object or an Array, the
old value will be the same as new value because they reference the
same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
So I have an object retrieved by props, so I should "disable" save button if object is equals to "original" or "enable" if object is different so if I make an update in frontend like modify property.
so If I enter in a page with my component I have original object like above described, and my save button is disabled because the "object" is not changed.
I would enable my save button if I change one of the properties of my object.
so example if I add a object in a property list array described, or if I change property message, or if I add a new property.
Watch function will be called when one of property in props object has been changed.
You can also use "v-bind" to pass all the properties of the object as props:
so
<demo v-bind="object"></demo>
will be equivalent to
<demo :id="object.id" :list="object.list" :propertyExample:"object.propertyExample" :message="object.message"></demo>
Then you can watch message prop individually for changes.
Edit
You can also use Vue Instance Properties.
There may be data/utilities you’d like to use in many components, but you don’t want to pollute the global scope. In these cases, you can make them available to each Vue instance by defining them on the prototype:
Vue.prototype.$appName = 'My App'
Now $appName is available on all Vue instances, even before creation. If we run:
new Vue({
beforeCreate: function () {
console.log(this.$appName)
}
})
Add watcher to that passed prop. and do something when changed.
watch: {
passedProp(changedObject) {
//do something...
change the variable which stands for enabling the "SAVE" button
}
}
OR if you are not using webpack/babel
watch: {
passedProp: function(changedObject) {
//do something...
change the variable which stands for enabling the "SAVE" button
}
}

Update template with model changed from input vueJS

I'm developing my first app in vueJs and laravel.
now I 'have a problem with v-model.
I have a page with component Person that edit or create new Person.
So I get from my backend in laravel or Model Person or new Person.
Now in my frontend I pass data to component by props:
Page.blade.php
<Person :person-data="{!! jsonToProp($person) !!}"></Person>
(jsonToProp transform model coming from backend in json)
In this case, I would return new Model so without properties, so $person will be a empty object.
Person.vue
<template>
<div>
<label for="name_p"> Name</label>
<input id="name_p" v-model="person.name" class="form-control" />
<button v-on:click="test()">test</button>
{{person.name}}
</div>
</template>
<script>
export default {
props: ['personData'],
mounted() {
},
data() {
return {
person: this.personData
}
},
methods:{
test(){
console.log(this.person.name);
}
}
}
</script>
Now if I change input with model v-model="person.name" I would print name in template but it doesn't change.
But if I click buttonit console write right value.
So I read that changing model value is asynch, so How I can render new Model when change input?
You should declare all the properties up front, as per the documentation:
Why isn’t the DOM updating?
Most of the time, when you change a Vue instance’s data, the view updates. But there are two edge cases:
When you are adding a new property that wasn’t present when the data was observed. Due to the limitation of ES5 and to ensure consistent behavior across browsers, Vue.js cannot detect property addition/deletions. The best practice is to always declare properties that need to be reactive upfront. In cases where you absolutely need to add or delete properties at runtime, use the global Vue.set or Vue.delete methods.
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).
That may be because your data, which comes from jsonToProp($person) does not reactive.
You see, vue modify each object to make it 'reactive', some times you need to modify it by your own. detection caveats
Try to do this.person = Object.assign({}, this.person, this.personData) in your mounted hook, to make it reactive.