Vue component updated (rerendered) without reason - vue.js

I have a component which is being updated (rerendered) without reason when parent's data item is changed even though it has nothing to do with component in question.
This is a simple reproduction https://codesandbox.io/s/gracious-cannon-s6jgp?file=/public/index.html if anyone can shed some light on this. (clicking button will fire update event in component)
If I remove element whose class is changed by vue (or remove dynamic class) it works as expected.

Because at each render you define new objects for the something property:
<hello-world :something="[{prop: 'vvv'},{prop: 'rrr'}]"></hello-world>
By clicking the button, you update the global data. The root element is rerendered.
During render, a new array with new objects is created as assigned at the something property in your component. While the objects created at each render are equal, they are different (i.e. they map to a different memory point)
Your component finds that the property something changes its reference, so it re-renders.
If you create an items property in your data and you pass that reference as the prop, the component is not re-rendered again:
main.js:
data: {
active: false,
items: [{ prop: "vvv" }, { prop: "rrr" }]
},
index.html:
<hello-world :something="items"></hello-world>
Note that this behavior occurs because you are passing an array 8and it would be the same with an object. It would not happen with a constant variable of the primitive types (such as string, int, boolean, float), such as :something="'string'"

Related

How to prevent props from reseting when child component updates?

avatarid and relationlist are passed from parent, when an image is uploaded, avatarid changed, but avatarid will be reseted to original if relationlist is changed (add or remove item from RelationTable component).
I think it is that theRelationTable rerendering causes the parent to reload. How can I stop such reseting when child component updates. Thanks.
<template>
<el-upload
class="avatar-uploader"
action
:http-request="uploadAvatar"
accept="image/jpeg,image/jpg,image/png"
:after-upload="uploadAvatarSucc"
>
<RelationTable ref="relationTable" :relationlist="relationlist" #delete="removeRelation" />
</template>
export default {
name: 'relation-component',
props: {
avatarid: {
type: String,
default: ''
},
relationlist: {
type: Array,
default: null
}
},
methods: {
uploadAvatarSucc(res) {
this.avatarid = res.imageId
},
removeRelation(index) {
if (this.relationlist.length > 0) {
this.relationlist.splice(index, 1)
}
}
}
}
You cant stop it - in Vue props are One-Way Data Flow only. If you open the browser Dev Tools you should see error message from Vue telling you exactly this.
Changing relationlist sort of works because you are updating the array in-place (modifying existing instance instead of replacing it ....which is not possible with numbers etc.) but if you try something else (for example creating new array with filter), it will stop working too.
Only correct way around it is to emit event and let the parent component (owner of the data) to do the modifications (google "props down events up"). This of course becomes harder and harder as the depth of the component tree and the distance between data owner and child component increases. And that's one of the reasons why global state management tools like Vuex exist....
Another way is to pass the data by single prop of type Object. As long as you only modify object's properties (not replacing the object itself), it will work. But this not recommended and considered anti-pattern...

What is proper component composition and use of props in Vue.js?

I'm struggling with this concept in vue.js..
I'm assuming that a component in Vue is an entity with some (html) representation and internal data or state. The component can then change it's internal data based on user's interaction with the template and inform the 'outer world' about its internal changes via events.
But then to put the component in context of the application as a whole most components need to receive data from the 'outer world' which would be done via props. So for a component to be useful it most often needs to change not only it's internal state but also some data it was given from the outer context - but props cannot be mutated directly. The internal data is for the internal working of the component but the real purpose of a component is to transform the data in props.
Lets say we have a component which is, via props, given an object representing a user profile for instance. The role of the component is to let the user edit their profile.
- to avoid mutating the prop (or a subproperties of the prop), i'd add a local copy of the prop which the component could work with freely - but i'd also have to add a watch to update the local copy every time the prop gets updated by the parent via v-bind.
</template>
<input v-model="localUserProfile.name"/>
</template>
<script>
export default {
props: {
userProfile: {
type: Object,
required: true
}
},
data: function () {
return {
localUserProfile: this.userProfile
}
},
watch: {
userProfile (newVal) { this.localUserProfile = newVal }
}
}
</script>
I could replace the watch with a computed property based on the given prop and let the component work over the computed property but then where to assign the edited values? Use the computed property's setter and 'emit' on changes?
Both these cases seem like a lot of extra code for a very common and repetitive task. What are some other common approaches to this? Are any of my assumptions wrong?
You should not update the prop from the children component :
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. This prevents child components from accidentally mutating the parent’s state, which can make your app’s data flow harder to understand.
In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component. If you do, Vue will warn you in the console.
You can read more about it here
If you have no other solution than updating if from the children component, it might be worth thinking about a different data flow strategy or design. (Components Basics)

At what point in the component lifecycle does a prop get injected into a component?

I have a Parent component that is passing an array, let's call it 'bucket' that contains a bunch of stuff, to a Child component.
In my parent component, I have the following:
<child-component
:bucket=bucket
></childcomponent>
In my child component, I have a props section, that accepts a prop called bucket, and has it defined as an array, like so:
props: {
bucket: Array
}
Now in the mounted section of my child component, I want to take this bucket of stuff, and do something with it. However, for some reason, it shows up as empty. So when I do this in my child component...
mounted() {
console.log(this.bucket.length)
}
... I get a 0. When I check Vue dev tools, I can see that there are items in the bucket array in the Child component's props section. Furthermore, if in the console, with the child component set as $vm0, when I type $vm0.bucket.length, I get the correct size.
What on Earth!? Is the prop not injected yet when mounted is called in the Child component? If so, when does this actually happen? How do I get around this? Super confused.
Thanks!
try to add directive v-if
<child-component :bucket="bucket" v-if="bucket.length"></childcomponent>
if you can get this.bucket.length on component mounted this time, maybe #Stephen Thomas is right.
As you said in the comments, you are pushing new items to the bucket array.
Instead pushing items into the array, you should create a new array with the new items, example:
this.bucket = [...this.bucket, newItem];
Vue reactivity works when the value is changed, pushing a new item into an array is doesn't change the variable's value.
When you attribute an Object or Array to a variable, it's memory address that is given to the variable, so changing it's internal attributes or pushing new items won't change it's memory address.

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)

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
}
}