How to get child component's data in VueJs? - vue.js

This is an easy question but I dont know the best way to do it correctly with Vue2:
Parent Component:
<template>
<div class="parent">
<child></child>
{{value}} //How to get it
</div>
</template>
Child Component:
<template>
<div class="child">
<p>This {{value}} is 123</p>
</div>
</template>
<script>
export default {
data: function () {
return { value: 123 }
}
}
</script>

Some ways you can achieve this include:
You can access the child VM directly via a ref, but this gets hairy because the ref won't be populated until the child component has mounted, and $refs isn't reactive, meaning you'd have to do something hacky like this:
<template>
<div class="parent">
<child ref="child"></child>
<template v-if="mounted">{{ $refs.child.value }}</template>
</div>
</template>
data() {
return {
mounted: false
}
},
mounted() {
this.mounted = true;
}
$emit the value from within the child component so that the parent can obtain the value when it is ready or whenever it changes.
Use Vuex (or similar principles) to share state across components.
Redesign the components so that the data you want is owned by the parent component and is passed down to the child via a prop binding.
(Advanced) Use portal-vue plugin when you want to have fragments of a component's template to exist elsewhere in the DOM.

Child components pass data back up the hierarchy by emitting events.
For example, in the parent, listen for an event and add the child value to a local one (an array for multiple child elements would be prudent), eg
<child #ready="childVal => { value.push(childVal) }"></child>
and in the child component, $emit the event on creation / mounting, eg
mounted () {
this.$emit('ready', this.value)
}
Demo ~ https://jsfiddle.net/p2jojsrn/

Related

multi-layer deep v-model data-binding in vue3

Suppose I have two components, one being just a simple input:
<!-- Child.vue -->
<template>
<input v-model="value" />
</template>
Now suppose I have a parent element, and I would like to v-model bind the child input's value.
Pseudo-code:
<!-- Form.vue -->
<template>
<Child v-model:value="parentVariable"/>
</template>
So that way the value of the Child's input lives outside of the state of the Child component. (Or it is present in both and are binded together.)
The question is how do I two-way data-bind a variable in a parent component, to an input in the child's component?
(I am using vue3)
(If there is a best practice for this, please inform me.)
A solution is documented in the docs
you can use emit and props to propagate v-model to the parent component
Example from docs:
app.component('custom-input', {
props: ['modelValue'],
emits: ['update:modelValue'],
template: `
<input v-model="value">
`,
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
})

Calling function inside child component without an event?

Currently trying to use a method belonging to the parent
<p class="message-date text-center">
{{ $emit('format_date_day_month_year_time', message.date) }}
</p>
However I am getting the error.
Converting circular structure to JSON
--> starting at object with constructor 'Object'
How can I call a function inside a child component that does not rely on an event? I apologize for asking such a simple question but everything I was able to find on google is using $emit and using an event.
$emit was designed to only trigger an event on the current instance of vue. Therefore, it is not possible to receive data from another component this way.
For your case, I would suggest to use Mixins especially if you need to use certain functions among multiple vue components.
Alternately, let the child component call the the parent through $emit then receive the result from the parent through a prop.
Your code could be something as follows:
Child component
<template>
<p class="message-date text-center">
{{ date }}
</p>
</template>
<script>
export default {
name: 'Child',
props: {
date: String,
},
mounted() {
this.$emit("format-date", message.date);
},
}
</script>
Parent component
<template>
<Child :date="childDate" #format-date="formatChildDate" />
</template>
<script>
import Child from '#/components/Child';
export default {
components: {
Child,
},
data: () => ({
childDate: '',
}),
methods: {
formatChildDate(date) {
this.childDate = this.formatDateDayMonthYearTime(date)
},
formatDateDayMonthYearTime(date) {
//return the formatted date
},
},
}
</script>
with $emit you call a function where the Parent can listento.
where you are using it i would suggest a computed prop of the function.
But back to your Question here is a example of emiting and listen.
//Parent
<template>
<MyComponent #childFunction="doSomethingInParent($event)"/>
</template>
//Child
<template>
<button #click="emitStuff">
</template>
.....
methods:{
emitStuff(){
this.$emit(childFunction, somedata)
}
with the event you can give Data informations to a Parentcomponent.

What EXACTLY does mutating a child via emitted event .sync modifier do?

So I've been investigating how mutating child props from the parent can be done.
I already encountered the pitfall with using v-model.
It throws this error:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "number1"
I learnt to deal with this problem by emitting events from the child and using .sync modifier in the parent.
However, I still feel like I didnt really understand whats going on under the hood in both cases.
As far as I understood, when mutating the childs properties from parent with v-model, the mutated data also mutates for any other parent importing the child.
To test this out, I built the following setup:
I have a child component with two inputs:
One using v-model, the other an emitted event to mutate the childs props.
We call it "compA":
<template>
<div>
<input
:value="number1"
#change="$emit('update:number1', $event.target.value)"
placeholder="number1emittedEvent"
/>
<input v-model="number1" placeholder="number1vmodel">
</div>
</template>
<script>
export default{
name: "compA",
props: {
number1: String
}
}
</script>
compA is being imported by compZ and compB. compZ also imports compB, and then compZ is finally being imported by myComplexView5.vue (my project uses routing).
compB:
<template>
<div>
<h1>compB containing compA</h1>
<compA/>
<p v-text="number1"></p>
</div>
</template>
<script>
import compA from "#/components/complexComponent5/compA.vue"
export default {
name: "compB",
components: {
compA
},
data(){
return {
number1:''
}
}
}
</script>
compZ:
<template>
<div>
<compA :number1.sync="number1"/>
<br>
<compB />
</div>
</template>
<script>
import compA from "#/components/complexComponent5/compA.vue"
import compB from "#/components/complexComponent5/compB.vue"
export default {
name: "compZ",
components: {
"compA" : compA,
"compB" : compB
},
data(){
return {
number1:''
}
}
}
</script>
myComplexView5.vue:
<template>
<div>
<h1>And More testing</h1>
<compZ />
</div>
</template>
<script>
import compZ from "#/components/complexComponent5/compZ.vue"
export default{
name: "myComplexView5",
components: {
compZ
}
}
</script>
Now, I expected the following behavior:
When inputting into the inputfield which has v-model, I'd expect the paragraph inside compB to display the changes, since its text value is bound to the number1 prop from child compA. Since I mutated the childs props directly, the changed value should show up in any of the childs parents (or grandparents and so on).
But it doesnt. And it gets even better: When I use the inputfield with the event emitting from child to parent, the paragraph inside compB receives the changes!
This is basically the opposite of what I've learned from the sync modifier docs:
In some cases, we may need “two-way binding” for a prop. Unfortunately, true two-way binding can create maintenance issues, because child components can mutate the parent without the source of that mutation being obvious in both the parent and the child.
That’s why instead, we recommend emitting events in the pattern of update:myPropName. For example, in a hypothetical component with a title prop, we could communicate the intent of assigning a new value with:
this.$emit('update:title', newTitle)
Then the parent can listen to that event and update a local data property, if it wants to
Maybe this is caused by the data() inside compB which sets number1 to EMPTYSTRING when the component rerenders? I don't know, I'm very new to vue and I don't really understand when components rerender. I also used this
data() {
return {
number1: ''
}
}
inside compB To prevent this error from occuring:
[Vue warn]: Property or method is not defined on the instance but referenced during render.
I don't really know how else to prevent this error from occuring, since it seems that even though compB imported the prop from compA, the prop still needs to be declared in compB Oo
EDIT:
I just found out that in the code I'm using, compZ still had this paragraph element:
<p v-text="number1"></p>
The string inputted into the inputfield appeared there, NOT in the paragraph element of compB. For some reason, even though I get no errors, neither through the emitted event from compA, nor through the v-model from compA does any change to the props inside compA show any impact inside compB... :(
I put your example code in a snippet at the bottom of the post so that we're on the same page about what code is actually being talked about (I removed the line with the <p v-text="number1"></p> from compB, because that component doesn't have a prop or data/computed property for number1).
Your compA component takes in a number1 prop, which it then uses for two inputs:
The first input uses the prop as its value and then emits an 'update:number1' event with the input's value in response to the input's change event. This allows you to specify the .sync modifier when the parent component binds a value to the compA component's number1 prop (as you do in compZ).
The second input directly binds the value of the number1 prop via v-model. This is not recommended (for reasons I'll explain), which is why you are seeing the "Avoid mutating a prop directly" warning. The effect of directly binding the number1 prop to this input is that whenever the value of this second input changes, the value of number1 will change, thus changing the value of the first input, and finally causing that first input to emit the update:number1 event. Technically Vue allows you to do this, but you can see how it can get confusing.
Your compB component simply renders some texts and a compA component without passing a value as a number1 prop to compA.
Your compZ component renders a compA component, binding its own number1 property value with the .sync modifier to the compA component. This compA component instance does not share any data with the compA component instance in the compB component, so we can't expect any changes to either component to affect the other.
Vue.component('compA', {
template: `
<div>
<input
:value="number1"
#change="$emit('update:number1', $event.target.value)"
placeholder="number1emittedEvent"
/>
<input v-model="number1" placeholder="number1vmodel">
</div>
`,
props: {
number1: String
}
})
Vue.component('compB', {
template: `
<div>
<h1>testString</h1>
<compA />
</div>
`
})
Vue.component('compZ', {
template: `
<div>
<compA :number1.sync="number1"/>
<br>
<compB />
</div>
`,
data() {
return {
number1: ''
}
}
})
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div>
<h1>And More testing</h1>
<comp-z/>
</div>
</div>

How do I use v-model on none form-related elements?

Before anything else, I'm using Vuetify's VSwitch component inside my-component. I want to return the value of my-component to the parent.
something like <my-component v-model="returnedData"></my-component>
Then the inside <my-component></my-component>
<template>
<div>
<v-switch v-model="toggledData" value="John"></v-switch>
<v-switch v-model="toggledData" value="Andrew"></v-switch>
<v-switch v-model="toggledData" value="Melissa"></v-switch>
<v-switch v-model="toggledData" value="Elizabeth"></v-switch>
</div>
</template>
<script>
export default {
props: ['value'],
data () {
return {
toggledData: []
}
}
}
</script>
I want to return the value of toggledData to the parent that's using it if possible. I've been browsing the net for a while and I've been seeing only with inputs. But it was possible to some of Vuetify's components like the VTreeviewso I was thinking maybe it's possible.
Using v-model like in your example:
<my-component v-model="returnedData"></my-component>
is (by default) equivalent to this:
<my-component :value="returnedData" #input="returnedData = $event"></my-component>
Any component can support v-model just by having a value prop and emitting an input event. The names of the prop and event can be changed using the model option, see https://v2.vuejs.org/v2/guide/components-custom-events.html#Customizing-Component-v-model
All of this assumes that you want two-way data binding. In your question you seem to imply that you just want to pass data up to the parent, which is only one way. For that you only need to emit an event and listen for that event using an # listener.
Genuinely creating a two-way data binding would be tricky in this case. The easiest way is to drop the v-model on the v-switch and use the prop and event separately. There are alternatives, such as using v-model with a computed property that has a getter and setter, but I'm not convinced that would make things any clearer.
The parent component listens to the child's changes with the childToParent and if there is any change I call the childChanged () method
// Parent Component
<template>
<div id="parent">
<p>{{parentToggledData}}</p>
<Child v-on:childToParent="childChanged"/>
</div>
</template>
<script>
import Child from "./Child";
export default {
name: "parent",
components: {
Child
},
data() {
return {
parentToggledData: []
};
},
methods: {
childChanged(value) {
this.parentToggledData = value;
}
}
};
</script>
I listen to the changes on each v-switch, and if there is one, I call the emitToParent () method in this method, I send the changes to the parent with $emit which takes as parameter the event listened by the parent childToParent and "the value to send this.toggledData
// Child Component
<template>
<div id="child">
<v-switch v-model="toggledData" value="John" #change="emitToParent"></v-switch>
<v-switch v-model="toggledData" value="Andrew" #change="emitToParent"></v-switch>
<v-switch v-model="toggledData" value="Melissa" #change="emitToParent"></v-switch>
<v-switch v-model="toggledData" value="Elizabeth" #change="emitToParent"></v-switch>
</div>
</template>
<script>
export default {
name: "child",
data() {
return {
toggledData: []
};
},
methods: {
emitToParent(event) {
console.log(event)
console.log(this.toggledData)
this.$emit("childToParent", this.toggledData);
}
}
};
</script>
You can do something like this:
<v-switch #change="$emit('swithValue', value)" value="John"></v-switch>
Then in your parent component just listen for swithcValue like #switchValue="myFunction" Your function implicitly gets the emitted value and you can do with it as you wish.

Undefined props in Vue js child component. But only in its script

I have just started using Vue and experienced some unexpected behavior. On passing props from a parent to child component, I was able to access the prop in the child's template, but not the child's script. However, when I used the v-if directive in the parents template (master div), I was able to access the prop in both the child script and child template. I would be grateful for some explanation here, is there a better was of structuring this code? See below code. Thanks.
Parent Component:
<template>
<div v-if="message">
<p>
{{ message.body }}
</p>
<answers :message="message" ></answers>
</div>
</template>
<script>
import Answers from './Answers';
export default {
components: {
answers: Answers
},
data(){
return {
message:""
}
},
created() {
axios.get('/message/'+this.$route.params.id)
.then(response => this.message = response.data.message);
}
}
</script>
Child Component
<template>
<div class="">
<h1>{{ message.id }}</h1> // works in both cases
<ul>
<li v-for="answer in answers" :key="answer.id">
<span>{{ answer.body }}</span>
</li>
</ul>
</div>
</template>
<script>
export default{
props:['message'],
data(){
return {
answers:[]
}
},
created(){
axios.get('/answers/'+this.message.id) //only worls with v-if in parent template wrapper
.then(response => this.answers = response.data.answers);
}
}
</script>
this.message.id only works with v-if because sometimes message is not an object.
The call that you are making in your parent component that retrieves the message object is asynchronous. That means the call is not finished before your child component loads. So when your child component loads, message="". That is not an object with an id property. When message="" and you try to execute this.message.id you get an error because there is no id property of string.
You could continue to use v-if, which is probably best, or prevent the ajax call in your child component from executing when message is not an object while moving it to updated.