How to read component values from parent vue - vue.js

I have an input field component I created. In the parent vue I added two of these components - field1 and field2. When either one of these components change I want to concatenate their values and add them to a third input field - field3. Field3 is not a vue component. I tried to add a ref to get the value of the component that did not emit the event, but it always comes back undefined (i.e. console.log(this.$refs.testRef.$data). What is the best way to get the value of field 1 and field2? Lastly how do I change the value of field 3 from the onChange method?
input-field.vue
<template>
<div>
<input type="text" #input="valueChanged">
</div>
</template>
<script>
export default {
name: 'myInput',
props:
['myName']
,
emits: ["input"],
methods:
{
valueChanged () {
this.$emit("input",event.target.value)
}
}
}
</script>
app.vue
<template>
<div>
<my-input myName="field1" #input="onChange"/>
<my-input ref="testRef" myName="field2" #input="onChange"/>
<input id="field3" type=text/>
</div>
</template>
<script>
import myInput from './components/input-field.vue'
export default {
name: 'App',
components: {
myInput
},
methods: {
onChange (val) {
//DOESNT WORK ----> console.log(this.$refs.testRef.$data)
console.log(val)
//get the value from field1 and the value from field2 two and combine them to set the value of field3
//i.e. field3.value = field1.value + '\n' + field2.value
}
}
}
</script>

Related

Why do I need the v-bind when I have the v-on?

In the tutorial of vue.js, we have this code
<script>
export default {
data() {
return {
text: ''
}
},
methods: {
onInput(e) {
this.text = e.target.value
}
}
}
</script>
<template>
<input :value="text" #input="onInput" placeholder="Type here">
<p>{{ text }}</p>
</template>
And I don't understand why when I delete the bind on value, the two way binding is still working ?
In the tuto, it says that using the v-on & v-bind allow to do two way binding
Am I missing something ?
The Vue example is sort of a bad use case, a little simple for what it's trying to convey:
v-on is for assigning event listeners, so v-on:click="doSomething(value)"
v-bind is binding the actual value of vue data/state. So example:
<button v-on:click="setUserDetails(value)" v-bind:value="user.id">Click</button>
Imagine this component:
<template>
<input :value="value"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
And now a simple usage of it:
<template>
<MyComp v-model="passwd" type="password" minlength="3" #focus="onFocus"/>
</template>
<script>
export default {
name: 'MyOtherComp',
data(){
return {
passwd: ''
}
},
methods:{
onFocus(){}
}
}
</script>
As you can see, value, type, and minlength properties and focus event are bidden to MyComp.
Now question: How can I handle extra props in MyComp? they are not defined in MyComp props. Vue gathers them in a special variable called $attrs, which is a normal JS object. Vue also gathers all events into $listeners variable.
Now inside MyComp these special variables are:
$atrrs:{
type: 'password',
minlength: '3'
}
$listerners:{
focus: /* function onFocus from parent */
}
To redirect these values:
<template>
<input :value="value" v-bind="$attrs" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
}
}
</script>
As you can see, we use v-bind to bind extra props, and we use v-on to bind (redirect) events. The result is:
<input :value="value" :type="$attrs.type" :minlength="$attrs.minlength" #focus="$listeners.focus"/>
Of course you can use these directions to bind you objects too:
<template>
<input :value="value" v-bind="$attrs" v-bind="accumulated" v-on="$listeners"/>
</template>
<script>
export default {
name: 'MyComp',
props:{
value: String
},
data(){
return {
accumulated:{
maxlenght: (+this.$attrs.minlength || 2) + 30, // It's just for a practice to use extra props inside JS code :-)
rows: 5,
}
}
}
}
</script>
Keep in mind that duplicate props will replace and the last one wins.

Vue 2 pass value from child to parent element (make custom styled input)

let say if i have a custom input
<template>
<input v-model="value"/>
</template>
<script>
export default {
name: "my-input",
props: {
value: String
}
}
</script>
how i can pass it value to parent element
<template>
<my-input v-model="test"/>
<button #click="check">see value</button>
</template>
<script>
export default {
methods: {
check() {
console.log(this.test);
}
}
}
</script>
when i press the button it shows undefined. i expected it to returns my-input value.
how i could pass value from it? i tried v-model and :value, both shows undefined.
Bind the input value to prop value then add #input event that emits the input value to parent component :
<template>
<input :value="value" #input="$emit('input', $event.target.value)"/>
</template>
<script>
export default {
name: "my-input",
props: {
value: String
}
}
</script>

Passing Custom Event back to child component through prop array in VueJs

I am trying to update a child component element through a custom event and know I am getting back to the root component as an alert gives me the current date and time, but I can not update the child component through the prop. This is a follow on question to this question and answer:
(irrelevant code omitted)
In my child component I have as follows:
<template>
<div id="bar">
<div class="row">
<div class="column">
<h3>Timestamp Page Loaded: </h3>
<h3>{{currentTime}}</h3>
</div>
<div class="column">
<button v-on:click="update()">Click to get Current Timestamp</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
barData: Array
},
data() {
return {
callNow: this.barData[0].callNow,
currentTime: this.barData[0].currentDT,
}
},
methods: {
update: function () {
const today = new Date();
const date = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate();
const time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
const dateTime = date + ' ' + time;
this.$emit('newDateTime', dateTime)
}
},
}
</script>
And in my root component I have as follows:
<template>
<div>
<bbay-bar v-bind:barData="barData" v-on:newDateTime='upDateTime($event)'></bbay-bar>
</div>
</template>
<script>
import Bar from './Components/SiteWide/Bar.vue'
export default {
components: {
'bbay-bar': Bar,
},
data() {
return {
barData: [{
callNow: '0411111111',
currentDT: '',
}],
}
},
methods: {
upDateTime: function (update){
//this.$alert(update, “thus so far“);
this.currentDT = update;
}
}
}
</script>
You're using props to initialize data in the child component, then using those values in the child component's template. Changes to props afterwards won't affect the child component's data, so the template isn't updated. This scenario is brought up in the Vue docs here.
If you don't require an array for barData, I'd recommend making it an object so your data look like this:
data() {
return {
barData: {
callNow: '0411111111',
currentDT: '',
},
}},
Then in the child component you could pass the prop like so:
props: {
barData: Object
},
When you use the value in the child component, it'd then look like this:
<h3>{{ barData.currentDT }}</h3>
One thing I noticed is that in the upDateTime function you are updating this.currentDT. Previously your data property had a barData array with an object that has a field named currentDT, so this method wasn't updating the correct field. Given that barData is changed to an object, the method should be changed to update this.barData.currentDT

Access slot component data?

I have the following setup:
CustomForm.vue
<template>
<div>
<input v-model="field1" >
<input v-model="field2" >
</div>
</template>
<script>
export default {
data () {
return {
field1: '',
field2: '',
}
}
}
</script>
Parent.vue
<template>
<div>
<child>
<template>
<custom-form />
</template>
</child>
</div>
</template>
<script>
import Child from ...
import CustomForm from ...
</script>
Child.vue
<template>
<div>
<button #click="click" />
<grand-child>
<template>
<slot></slot>
</template>
</grand-child>
</div>
</template>
<script>
import GrandChild from...
export default {
methods: {
click: function () {
var data = ... // get form data
// do something with data then $emit
this.$emit('custom-click', data)
}
}
}
}
</script>
GrandChild.vue
<template>
<div v-for="(item, index) in list" :key="index" >
<input ...>
<input ...>
<slot></slot>
</div>
</template>
Basically I have a CustomForm, I want to pass the form to GrandChild.vue from Parent.vue, but the issue is I don't know how do I retrieve CustomForm data (field1, field2) in Child.vue ie how do I get CustomForm value from click method in Child.vue? Thanks
Instead of trying to extract data from a slot, there are other approaches. One solution is to use Vue's provide/inject feature to inject the form's data.
First, setup CustomForm to allow v-model to capture the form data in Parent:
Upon submitting the form in CustomForm, emit the input event with the form's data.
Add a value prop, as required for `v-model.
CustomForm.vue:
<template>
<form #submit.prevent="submit"> <!-- (1) -->
</form>
</template>
<script>
export default {
props: ['value'], // <-- (2)
methods: {
submit(e) { // <-- (1)
const form = e.target
const formData = {}
for (const [key, value] of new FormData(form).entries()) {
formData[key] = value
}
this.$emit('input', formData)
}
}
}
</script>
Then Parent can bind the form data:
Define parentForm in Parent's data as an object with a subproperty (e.g., parentForm.data).
Bind that subproperty to CustomForm's v-model.
Parent.vue:
<template>
<child>
<custom-form v-model="parentForm.data" /> <!-- (4) -->
</child>
</template>
<script>
export default {
data() {
return {
parentForm: { // <-- (3)
data: {}
}
};
}
}
</script>
Now, Parent can provide form:
Declare a provide method that returns an object with a form subproperty.
Set that property's value to the parentForm property previously declared in (3).
Parent.vue:
export default {
provide() { // <-- (5)
return {
form: this.parentForm // <-- (6)
}
}
}
...and Child or GrandChild can inject form:
Declare an inject property, whose value is a string array containing form (subproperty name from (5)).
GrandChild.vue:
export default {
inject: ['form'] // <-- (7)
}
demo

How to use v-model and props passed to parent from child?

I've been learning vue for a few days now and I'm trying out passing data/props between child and parent.
Now I have the following child:
<template>
<div>
<input v-model="name1" placeholder="string">
<input v-model="number1" placeholder="number">
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
}
}
</script>
And then parent:
<template>
<div>
<child/>
</div>
</template>
<script>
import child from "#/components/complexComponent4/child.vue"
export default{
name: "parent",
components: {
child
}
}
</script>
Now when I enter some text into the input fields, it displays correctly in the paragraphs, since the props bound to the paragraphs have changed.
However, I get this warning:
[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: "name1"
found in
---> <Child>
<Parent> at src/components/complexComponent4/parent.vue
<MyComplexView4.vue> at src/views/myComplexView4.vue
<App> at src/App.vue
<Root>
I read about this error in multiple places on the internet and also in the documentation, and I found that mutating props is deemed an anti-pattern now:
https://michaelnthiessen.com/avoid-mutating-prop-directly
Unfortunately I didn't really find anything specific and/or helpful on how to deal with this problem. Especially in context of vue handling primitive data and objects/arrays differently (objects/arrays are passed by reference).
v-model seems to play an important role in leveraging the power of vue, since it enables two-way binds. Therefore I wouldn't want to omit it entirely, unless its use has become so difficult that it doesnt justify the gains.
As the warning says, you should avoid mutating props directly in child component.
So you should emit an event from child to parent to let parent know that prop value had been changed. Parent will change the prop and pass it down to child.
For such purpose there is a syntactic sugar in Vue called .sync modifier.
So your components could be something like this.
Child:
<template>
<div>
<input
:value="name1"
#change="$emit('update:name1', $event.target.value)"
placeholder="string"
/>
<input
:value="number1"
#change="$emit('update:number1', $event.target.value)"
placeholder="number"
/>
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
}
}
</script>
And parent:
<template>
<div>
<child :name1.sync="name1" :number1.sync="number1"/>
</div>
</template>
<script>
import child from "#/components/complexComponent4/child.vue"
export default{
name: "parent",
components: {
child
},
data() {
return {
name1: '',
number1: ''
}
}
}
</script>
Or for more complicated cases you can either use v-model and computed properties with setters in child component:
<template>
<div>
<input
v-model="computedName1"
placeholder="string"
/>
<input
v-model="computedNumber1"
placeholder="number"
/>
<p v-text="name1"></p>
<p v-text="number1"></p>
</div>
</template>
<script>
export default {
name: "child",
props: {
name1 : String,
number1 : Number
},
computed: {
computedName1: {
get() { return this.name1 },
set(value) {
// some logic
this.$emit('update:name1', value)
},
computedNumber1: {
get() { return this.number1 },
set(value) {
// some logic
this.$emit('update:number1', value)
}
}
}
}
</script>
If you intend to change prop passed down to child assign it first to child data.
<template>
<div>
<input v-model="name" placeholder="string">
<input v-model="number" placeholder="number">
<p v-text="name"></p>
<p v-text="number"></p>
</div>
</template>
<script>
export default {
name: "child",
data() {
return {
name: null,
number: null
}
},
props: {
name1 : String,
number1 : Number
},
mounted() {
this.name = this.name1;
this.number = this.number1;
}
}
</script>
When data is changed you can $emit those changes to parent component
With sync
Parent
<child :number1.sync="number1" :name1.sync="name1" />
Child
watch: {
name: value => this.$emit('update:name1', value)
number : value => this.$emit('update:number1', value)
},
With events
Parent
<child :number1="number1" :name1="name1" #changeNumber="value => number1 = value" #changeName="value => name1 = value" />
Child
watch: {
name: value => this.$emit('changeName', value)
number : value => this.$emit('updateNumber', value)
},
A guideline for vue.js is that you can use props to automatically alter data in the child from the parent, but not vice-versa. For altering data of the parent-component, the child-component is supposed to use events. You could consider using two different components for name1 and number1 respectively and bind the values in a two-way-manner by making these components applicable for v-model, as it is described here.