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)
Related
I have a Vue Component that is a form for creating (or editing) a patient. It contains a lot of information: birthname, firstname, date of birth, birth place, gender, etc.
What are the best practices in terms of maintainability, reactivity and performance to pass those information?
Is this recommended to pass each information item as a prop?
<patient-form
:firstname.sync="patient.firstname"
:lastname.sync="patient.lastname"
:birthdate.sync="patient.birthdate"
... />
Or more recommended to use an object Patient with all the information?
<patient-form :patient="patient" #update="updatePatient"/>
Personally, I prefer the second approach. It looks cleaner. Whenever you want to add a new property like age you don't have to worry about adding it both in the parent and the child component the object finishes the job. And reactivity wise toRefs can handle it like this.
Or simple snippet
<script setup>
import { toRefs, defineProps } from 'vue'
const props = defineProps({
patient: {
type: Object
}
})
const { patient } = toRefs(props)
But there is also another way in addition to illustrated in the comment section. Let me guess you are fetching the data from server, so you can just pass the id of the patient and fetch inside your child component.
Your requirement is totally depends on the use case of <patient-form> component and the data you are using in this component.
If you have multiple reference of <patient-form> in a parent component. i.e in case of edit the parent information against each parent record. In this case, I will suggest to get the real time parent information from a database through an API call in the child component itself.
Hence, As per my understanding the best approach would be, pass the parent id as a prop in the <patient-form> component and then get the real time updated data by calling an API based on patient id and bind the response in the template. Then on successful edit, you can post the data and emit the success event to parent.
In parent component :
<patient-form :patientid="patient.id" #update="isUpdateSuccess"/>
In child component :
mounted() {
// get parent data based on the `patientid` parameter and bind that in the template.
}
methods: {
onUpdate(id) {
// post the updated data with the help of an API call.
// emit the success event on parent else show the error in the child itself.
}
}
But if still you already have the patient data in your parent component and you want to pass that in the <patient-form> component, Then your second solution is better than the first one. Go for that.
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.
I'm using v-model in custom component, but when I try to get the data(value props) of the child component in the parent component, then I get the old value, although the data in the parent has changed. If I get the data of the child component through setTimeout, then I get the data I need. How do I synchronize the v-model #input event and the data I receive from the child element in the parent?
This is the logic: there are AppForm(parent) component and AppSelect(child) component. I'm binding v-model on AddSelect, and I follow the changes through watch() { v-model-data }. Then v-model data has changed I call AppForm.data() method, which iterates through the list of children and gets the value prop of AppSelect component, but this value is old.
Short example:
https://codesandbox.io/s/serene-wildflower-5seqx?file=/src/App.vue
You're trying to get the child component's prop inside a watcher watching the data property that the prop is bounded to. Of course, the watcher triggers first, then the data is passed onto the child component and the prop is updated. Its not a good design if you are relying on the order that Vue events and functions are fired, but if you must, you can use vm.$nextTick() function to get values after everything is synced.
watch: {
selectData(newV, oldV) {
alert("Select data from parent component: " + newV);
this.$refs.form.data();
this.$nextTick(() => {
alert("Show select data after 2sec");
this.$refs.form.data();
}, 2000);
},
},
Although, I'd suggest you shift the watcher inside the child component and access the data prop there.
I know we can pass data to child components via Props. But it is reactive in one-way data flow mode. If the value of the data is changed in Parent component, it also has effect (update) on props in Child component.
In my case, I don't want to get update on specific prop in Child component even if that data in the Parent component is changed. It is because Child component will only responsible to show the data. But the data in the Parent Component still has to be reactive in Parent Scope.
I've read some forum article that suggest to use like Json. I feel it is a little dirty way and the data in my case is just one string.
Is there anyways to achieve that kind of solution?
You could copy the reactive prop in the created hook of the child component. The copy would not be reactive e.g.
export default {
props: {
reactive: Object
},
data: () => ({
nonreactive: null
}),
created() {
this.nonreactive = Object.assign({}, this.reactive)
}
}
Note: the way you copy the reactive prop will depend on the data type, the way I've shown will work for objects.
Maybe u can check this one
VueJS render once into an element
use v-once on your child component
Use prop as data property in child component. please see the fiddle link:
link here
Vue.component('greeting', {
props: ['user'],
data:function(){
return {
newuser:this.user
}
},
template: '<h1>hi {{ newuser }}</h1>'
});
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