Pass Function as Property to Vue Component - vue.js

I am trying to make my Vue Component reusable but there is a part in it which requires to run a function on button click which I have defined in the parent component.
The component's button will always run a parent function and the parameter it passes is always the same (its only other property).
Right now I am passing 2 properties to the component: 1) an object and 2) the parent function reference, which requires the object from 1) as a parameter.
The Child-Component looks like this (stripped unnecessary code):
<button v-on:click="parentMethod(placement)">Analyze</button>
Vue.component('reporting-placement', {
props: ['placement', 'method'],
template: '#reporting-placement',
methods: {
parentMethod: function(placement) {
this.method(placement);
}
}
});
The parent is making use of the child like this:
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement"></reporting-placement>
methods: {
analyzePlacement: function(placement) {
this.active_placement = placement;
},
}
As you can see, the child has only one property, placement, and the callback reference. The placement must be put in as a parameter to the reference function from the parent.
But since the parent defines the parameters, the child shouldn't concern itself with what it needs to pass to the parent function. Instead I would prefer to already pass the parameter along in the parent.
So instead of
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement"></reporting-placement>
I would prefer
<reporting-placement v-bind:placement="placement" v-bind:method="analyzePlacement(placement)"></reporting-placement>
(including appropriate changes in the child).
But passing the parameter along does not work that way.
Is it possible (maybe in other syntax) to 'bind' the variable to the function reference so that it is automatically passed along when the callback is called?
Info: I don't get an error message if I write it down as above but the whole Vue screws up when I pass the parameter along to the component.
Hope the issue is clear :-) Thanks a lot!

By reading your proposal I've found out that you are overusing the props passing.
Your concern that child component should not have any knowledge about the way that the parent component uses the data is completely acceptable.
To achieve this you can use Vue's event broadcasting system instead of passing the method as props.
So your code will become something like this:
Vue.component('reporting-placement', {
props: ['placement', 'method'],
template: '#reporting-placement',
methods: {
parentMethod: function(placement) {
this.$emit('reporting-placement-change', placement)
}
}
});
And you can use it like this:
<reporting-placement v-bind:placement="placement" #reporting-placement-change="analyzePlacement($event)"></reporting-placement>
But if you need the data which is provided by the method from parent it's better to consider using a state management system (which can be a simple EventBus or event the more complex Vuex)
And finally, if you really like/have to pass the method as a prop, You can put it in an object, and pass that object as prop.

Related

Vue mutate prop correctly

I'm trying to create a simple component whose focus is to display an element in an array, but I'm having issues with Vue's philosophy.
As you may know, if a mutation on a prop is triggered, Vue goes crazy because it doesn't want you to update the value of a prop. You should probably use a store, or emit an event.
The issue is: that since I'm adding functionalities to my codebase (for instance the possibility to start again when I reach the last element of the array), it would be wrong to have an upper component be responsible for this management, as it would be wrong to ask an upper component to change their variable, given that my component is supposed to manage the array, so an emit would be a bad solution.
In the same way, given that I'm making a generic component that can be used multiple times on a page, it would be incorrect to bind it to a store.
EDIT: the reason why the prop needs to be updated is that the component is basically acting as a <select>
Am I missing an obvious way to set this up?
To give an example of my end goal, I'm aiming for a component looking like the one in the picture below, and I think a 2 way bind like in v-model would be more appropriate than having to set an #change just to say to update the value of the passed prop.
If you have a prop the correct way to update the value is with a sync, as in the following example
Parent:
<my-component :title.sync="myTitle"></my-component>
Child:
this.$emit("update:title", this.newValue)
Here is a very good article talking about the sync method.
By the other hand you can alter a Vuex state variable by calling a Vuex mutation when you change the value:
computed: {
title: {
// getter
get() {
return this.$store.state.title
},
// setter
set(newValue) {
this.setTitle(newValue) // Requires mutation import, see the methods section.
// Or without import:
this.$store.commit('setTitle', newValue);
}
}
},
methods: {
...mapMutations("global", ["setTitle"]) // It is important to import the mutation called in the computed section
}
In this StackOverflow question they talk about changing state from computed hook in Vue. I hope it works for you.

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)

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)

Difference in Vue between data() and adding data in created()

Is there a difference between the following? I've seen examples doing both and am unsure why you would choose one over the other.
Vue.component('test', {
data() {
return { myDataA: 10 };
}
//vs
created() {
this.myDataB = 10;
}
}
Variables set in created() on this will not be reactive. In order for them to be reactive, you must define them in the object returned by data().
Example (note how the text does not change in the output):
https://jsfiddle.net/oyf4quyL/
in a component, there are three places where you can define you data:
data properties
computed properties
props
the created property is lifecycle hook in Vue. what this means, is that the Vue will run this function when the component is created. there are also other lifecycle hooks in Vue you can use, like mounted or beforeMount or beforeCreate and etc.
now with this in mind, let's answer your question.
when you define myDataA in data property, Vue will automatically create some "watchers" for this data property, so anytime that you set a new value to myDataA, anywhere that is using it, will be called again. but when you define a property directly on Vue instance (this), you will lose this "watchers" feature. (which by the way is just some getters and setters!)
so as i said, the best way and the correct way to define a data property is on any of the three places that i mentioned, based on your need. (because each of them has different use-cases that the others).

Changing a property of an object in child component using VueJs

I have a simple question but I can't find the answer to it.
This code (that belongs to a child component? is a good practice?
export default {
name: 'Wish',
props: ['wish'],
data () {
return {
isEditing: false
}
},
methods: {
completeWish () {
this.wish.done = true
},
...
Or I should update the done property of wish object in the parent using emit?
Thanks
As Amade mentioned is a bad practice.
You should use events in order to accomplish it properly from a design point of view.
<script>
export default {
name: 'Wish',
props: ['wish'],
methods: {
completeWish () {
this.$emit('wish-is-completed')
}
}
}
</script>
And then in parent you have v-on:wish-is-completed="handleCompletedWish" like:
// Parent template
<template>
<wish v-on:wish-is-completed="handleCompletedWish"></wish>
</template>
EDIT:
I understand the answer was downvoted because you actually are able to mutate properties of props (not a direct prop reference) and don't get a warn when you do that.
Does it mean you should do that?
No.
Props are created for one-directional data flow purpose. When you mutate props to notify a parent of something, it quickly leads to having hard to maintain state changes, because they are not implicit. Use events for child->parent communication as official documentation suggest in such cases.
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.
Vue docs advise against mutating the props in the child 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.
So based on this I would tend to say it's a bad practice.
documentation:
Due to the limitations of modern JavaScript (and the abandonment of Object.observe), 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
if you want add or change property use Vue.set like this:
this.$set(this.wish, 'done', true)
see example App:
https://3xyx386q65.codesandbox.io/setProps
see example code (open components/setProps/childComponent.vue):
https://codesandbox.io/s/3xyx386q65
note! wish init as Object! See components/setProps/setProps.vue in example code