How do I access a value in a child component in vue - javascript

Vcode is in a child component.
data() {
return {
vcode: null,
};
},
I need to access this value in a parent component method.
verifyCode() {
const code = this.vcode
}
Attempting this returns undefined. How do I access this value?
Update
I tried the suggestions and I still get an undefined value
Input field on child component
<input
class="form-control mt-5"
v-model.trim="vcode"
:class="{'is-invalid' : $v.vcode.$error }"
#input="$v.vcode.$touch(), vcodenum = $event.target.value"
placeholder="Enter your 6 digit code"
/>
On the parent component I added the following where it references the child component
<step2 ref="step2" #on-validate="mergePartialModels" v-on:vcodenum="vcode = $event"></step2>
My method in the parent component
verifyCode() {
const code = this.vcode
console.log(code)
}
I still get undefined.
I also tried this:
Child component
<input
class="form-control mt-5"
v-model.trim="vcode"
:class="{ 'is-invalid': $v.vcode.$error }"
#input="$v.vcode.$touch(), onInput"
placeholder="Enter your 6 digit code"
/>
Props
props: {
value: {
type: [String, Number, Boolean],
default: "",
},
},
method
onInput(e) {
this.$emit('on-input', e.target.value)
},
Parent
<step2 ref="step2" #on-validate="mergePartialModels" :value="vcode" #on-input="handleInput"></step2>
data() {
return {
vcode: null
};
},
method
handleInput(value) {
this.vcode = value
console.log(this.vcode)
},
The value ends up outputting null.
If I use the v-bind I get this error:
:value="value" conflicts with v-model on the same element because the latter already expands to a value binding internally

You can listen to the child's input event and send the value to the parent.
// InputComponent.vue
<input :value="value" #input="onInput" />
....
props: {
value: {
type: [String, Number, Boolean] // Add any custom types,
default: ''
}
},
methods: {
onInput(e) {
this.$emit('on-input', e.target.value)
}
}
// Parent.vue
<InputComponent :value="vCode" #on-input="handleInput" />
....
data() {
return {
vcode: null
}
},
methods: {
handleInput(value) {
this.vode = value
}
}

Related

Binding child input :value to the parent prop

I try to bind child input value to the parent value. I pass value searchText from a Parent component to a Child component as prop :valueProp. There I assign it to property value: this.valueProp and bind input:
<input type="text" :value="value" #input="$emit('changeInput', $event.target.value)" />
The problem is that input doesn't work with such setup, nothing displays in input, but parent searchText and valueProp in child update only with the last typed letter; value in child doesn't update at all, though it is set to equal to searchText.
If I remove :value="value" in input, all will work fine, but value in child doesn't get updated along with parent's searchText.
I know that in such cases it's better to use v-model, but I want to figure out the reason behind such behavior in that case.
I can't understand why it works in such way and value in child component doesn't update with parent's searchText. Can you please explain why it behaves in that way?
Link to Sanbox: Sandbox
Parent:
<template>
<div>
<Child :valueProp="searchText" #changeInput="changeInput" />
<p>parent {{ searchText }}</p>
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
name: "Parent",
components: { Child },
data() {
return {
searchText: "",
};
},
methods: {
changeInput(data) {
console.log(data);
this.searchText = data;
},
},
};
</script>
Child:
<template>
<div>
<input type="text" :value="value" #input="$emit('changeInput', $event.target.value)" />
<p>value: {{ value }}</p>
<p>child: {{ valueProp }}</p>
</div>
</template>
<script>
export default {
emits: ["changeInput"],
data() {
return {
value: this.valueProp,
};
},
props: {
valueProp: {
type: String,
required: true,
},
},
};
</script>
You set the value in your Child component only once by instantiating.
In the data() you set the initial value of your data properties:
data() {
return {
value: this.valueProp,
};
},
Since you don't use v-model, the value will never be updated.
You have the following options to fix it:
The best one is to use v-model with value in the Child.vue
<input
type="text"
v-model="value"
update value using watcher
watch: {
valueProp(newValue) {
this.value = newValue;
}
},
use a computed property for value instead of data property
computed: {
value() {return this.valueProp;}
}
Respect for creating the sandbox!
You are overwriting the local value every time the value changes
data() {
return {
value: this.valueProp, // Don't do this
};
},
Bind directly to the prop:
<input ... :value="valueProp" ... />

Props passed to child components returned as undefined in Nuxt

I have a child component inside of a form that takes an object as a prop from the parent component. This object is being used to store data entered from the user via input and is emitted whenever the user types. Now I will be honest, I'm not 100% sure that the emitting works just yet. This is due to the fact that when I attach properties of the object to v-model inside an input element, Nuxt shows an error saying
Cannot read property 'insert property here' of undefined
What puzzles me is the fact that if I look inside of Vue dev tools, it's shown clearly that the prop is received. I can view all of its properties when I click the child component, yet when I try to reference it I am given an error.
Here is how I pass the data to the child component from the parent file.
<NewProjectIterations
v-for="i in itterations"
:key="i"
:index="i"
v-model="itterationsData[i]"
/>
Here is the javascript
export default {
name: 'NewProject',
components: {
NewProjectIterations
},
data() {
return {
itterations: 2,
payoutAmount: null,
projectName: null,
emailAssigned: null,
itterationsData: []
}
},
computed: {
// Computed property that keeps all the form data inside of one object
formData() {
return {
user: this.$store.auth.user._id,
projectName: this.projectName,
payoutAmount: this.payoutAmount,
payoutDividedInto: this.itterations,
emailAssigned: this.emailAssigned,
itteration: this.itterationsData
}
}
},
mounted() {
// Initialize the child components data object on mount inorder to store user submit data
const object = {
name: '',
title: '',
startDate: null,
endDate: null,
payoutAmount: this.payoutAmount / this.itterations,
filesAttachhed: null,
index: null
}
for (let i = 0; i < this.itterations; i++) {
object.index = i + 1
this.itterationsData.push(object)
}
},
methods: {
increaseItteration() {
if (this.itterations > 5) {
alert('Itterations cannot be more than 5, nice try')
return
}
const object = {
name: '',
title: '',
startDate: null,
endDate: null,
payoutAmount: this.payoutAmount / this.itterations,
filesAttachhed: null,
index: this.itterations + 1
}
this.itterationsData.push(object)
this.itterations = this.itterations++
},
removeItteration() {
if (this.itterations > 1) {
alert('Itterations must be more than 1, nice try')
return
}
this.itterations = this.itterations--
this.itterationsData.pop()
},
removeItterationByIndex(index) {
this.itterations = this.itterations--
this.itterationsData.splice(index)
this.itterationsData.forEach(({ index }, i) => {
index = i + 1
})
},
async submitProject() {
// Submit the data and make a request to the backends API through Vuex & store the data
// inside of a database
await this.$store.dispatch('projects/createNewProject', this.formData)
}
}
Finally here is the <NewProjectItteration /> single file component.
<template>
<div :id="index" class="New-Project-Iteration">
<div class="New-Iteration-Upper-Half">
<div class="New-Iteration-Index">
<p>{{ index + 1 }}</p>
</div>
<div class="New-Interation-Header">
<input
id="title"
v-model="value.name"
type="text"
placeholder="Enter Title"
name="title"
style="display: block"
/>
<input
id="date"
v-model="value.startDate"
placeholder="Start"
class="textbox-n"
type="text"
onfocus="(this.type='date')"
onblur="(this.type='text')"
/>
-
<input
id="date"
v-model="value.endDate"
placeholder="Deadline"
class="textbox-n"
type="text"
onfocus="(this.type='date')"
onblur="(this.type='text')"
/>
</div>
</div>
<div class="New-Iteration-Lower-Half">
<p class="New-Iteration-Files">
No Files Attached
</p>
<p class="New-Iteration-Money">
$<input
id="money"
v-model="value.payoutAmount"
type="number"
placeholder="00.00"
step="0.01"
min="0"
name="money"
/>
</p>
</div>
</div>
</template>
and its javascript
export default {
name: 'NewProjectIteration',
props: ['index', 'value'],
watch: {
value() {
this.$emit('input', this.value)
}
}
}
Part of me has a feeling it has to do with Vue's lifestyle hooks and I'm using them incorrectly. Regardless I wanted to ask this question in case that anyone else would ever run into the same problem as me and wanted to read a detailed response.

How to update a child component when incrementing it in the parent component?

The problem: Whenever I increment the input field provided by the child component, the value doesn't set back to zero. It assumes the value of the previous instance.
Note: The increment is implemented in parent component method
Child component
<input type="number" placeholder="Amount" :value="value" #input="$emit('input',$event.target.value/>
<script>
export default {
props: ["value"],
data() {
return {};
}
};
</script>
Parent Component
<template>
<div>
<form-input v-for="n in count" :key="n" v-model="expense"> </form-input>
<button #click="addInputs">Add Expense</button>
<button #click="deleteInputs">Delete</button>
</div>
</template>
export default {
components: {
"form-input": formInput
},
name: "form",
data() {
return {
count: 0,
earnings: "",
expense: ""
};
},
methods: {
addInputs: function() {
this.count++;
},
deleteInputs: function() {
this.count--;
}
}
};
</script>
Please feel free to ask any questions if there any more needed information
Why are you passing a value prop from the parent anyway? Shouldn't the value of the child be self-controlled?
Try removing the binding of value.
<input type="number" placeholder="Amount" #input="$emit('input',$event.target.value/>`

Vue JS prop error for value on radio button with v-model and v-bind="$attrs"

I am getting some strange behaviour that I cannot wrap my head around.
I have a simple radio button component that's used as a "wrapper" for an actual radio button.
On this component, I have inheritAttrs: false and use v-bind="$attrs" on the element itself so I can use v-model and value etc.
However, upon selecting a radio button, an error is thrown that the prop value is invalid (because it's an event and not a string) and interestingly I noticed that on initial render the value prop is blank in Vue Devtools.
I'm simply trying to get these radio buttons updating the parent's data object value for location with a string value of the radio button selected.
I can't figure out where I'm going wrong here exactly. Any help greatly appreciated.
Example project of the problem:
https://codesandbox.io/embed/m40y6y10mx
FormMain.vue
<template>
<div>
<p>Location: {{ location }}</p>
<form-radio
id="location-chicago"
v-model="location"
value="Chicago"
name="location"
label="Chicago"
#change="changed"
/>
<form-radio
id="location-london"
v-model="location"
value="London"
name="location"
label="London"
#change="changed"
/>
</div>
</template>
<script>
import FormRadio from "./FormRadio.vue";
export default {
name: "FormMain",
components: {
FormRadio
},
data() {
return {
location: ""
};
},
methods: {
changed(e) {
console.log("Change handler says...");
console.log(e);
}
}
};
</script>
FormRadio.vue
<template>
<div>
<label :for="id">
{{ label }}
<input
:id="id"
type="radio"
:value="value"
v-on="listeners"
v-bind="$attrs"
>
</label>
</div>
</template>
<script>
export default {
name: "FormRadio",
inheritAttrs: false,
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
},
value: {
type: String,
required: true
}
},
computed: {
listeners() {
return {
...this.$listeners,
change: event => {
console.log("Change event says...");
console.log(event.target.value);
this.$emit("change", event.target.value);
}
};
}
}
};
</script>
Edit
Found this neat article which describes the model property of a component. Basically it allows you to customise how v-model works. Using this, FormMain.vue would not have to change. Simply remove the value prop from FormRadio and add the model property with your own definition
See updated codepen:
FormRadio Script
<script>
export default {
name: "FormRadio",
inheritAttrs: false,
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
}
},
// customize the event/prop pair accepted by v-model
model: {
prop: "radioModel",
event: "radio-select"
},
computed: {
listeners() {
return {
...this.$listeners,
change: event => {
console.log("Change event says...");
console.log(event.target.value);
// emit the custom event to update the v-model value
this.$emit("radio-select", event.target.value);
// the change event that the parent was listening for
this.$emit("change", event.target.value);
}
};
}
}
};
</script>
Before Edit:
Vue seems to ignore the value binding attribute if v-model is present. I got around this by using a custom attribute for the value like radio-value.
FormMain.vue
<form-radio
id="location-chicago"
v-model="location"
radio-value="Chicago"
name="location"
label="Chicago"
#change="changed"
/>
<form-radio
id="location-london"
v-model="location"
radio-value="London"
name="location"
label="London"
#change="changed"
/>
The input event handler will update the v-model.
FormRadio.vue
<template>
<div>
<label :for="(id) ? `field-${id}` : false">
{{ label }}
<input
:id="`field-${id}`"
type="radio"
:value="radioValue"
v-on="listeners"
v-bind="$attrs"
>
</label>
</div>
</template>
<script>
export default {
name: "FormRadio",
inheritAttrs: false,
props: {
id: {
type: String,
required: true
},
label: {
type: String,
required: true
},
radioValue: {
type: String,
required: true
}
},
computed: {
listeners() {
return {
...this.$listeners,
input: event => {
console.log("input event says...");
console.log(event.target.value);
this.$emit("input", event.target.value);
},
change: event => {
console.log("Change event says...");
console.log(event.target.value);
this.$emit("change", event.target.value);
}
};
}
}
};
</script>
See forked codepen
I removed the v-model entirely from the child component call (this was conflicting);
<form-radio
id="location-chicago"
value="Chicago"
name="location"
label="Chicago"
#change="changed"
/>
<form-radio
id="location-london"
value="London"
name="location"
label="London"
#change="changed"
/>
I then updated your changed method to include to set the location variable
methods: {
changed(e) {
console.log("Change handler says...");
console.log(e);
this.location = e;
}
}
Updated: Link to updated CodeSandbox

VueJS pass default prop without reference to child component

I've stumbled upon this situation where I want to pass a prop to a child component that will be the default value of the component, but it will only be showed when the initial value is empty.
Parent Component:
<multi-line-input v-model="data.something" placeholder="Enter Something" :default="data.something"/>
Child Component
props: {
value: {
type: String,
default: ''
},
default: {
type: String,
default: ''
},
},
methods: {
emitBlur (e) {
if (!this.value && this.default) {
this.value = this.default
}
this.$emit('blur')
},
emitInput () {
this.$emit('input', this.$el.value)
}
}
So what I am trying to achieve basically, is when the component loads will get the value from v-model it will also receive a default value that shouldn't change, and only used as a value when the actual value is empty on blur
The default will have the initial value of data.something and it should not change!
I tried to get rid of the reference using JSON.parse(JSON.stringify(this.value)) but it doesn't seem to work either!
So if I understand your question correctly, you want this behavior: upon the blur event on your <multi-line-input> component, if the value of the input is empty, then set the value to a default value which is specified by the parent (through a prop).
First of all, it is an error to do this.value = ... in your component. You must not modify props, props pass data from parent to child only, the data passed through props is not yours to modify directly from within the component.
Try something like this:
Vue.component('multi-line-input', {
template: '<input #blur="onBlur" #input="onInput" :value="value">',
props: {
value: {
type: String,
default: '',
},
default: {
type: String,
default: '',
},
},
methods: {
onBlur() {
if (!this.value && this.default) {
this.$emit('input', this.default);
}
},
onInput(e) {
this.$emit('input', e.target.value);
},
},
});
new Vue({
el: '#app',
data: {
user: null,
initialUser: null,
},
created() {
// Pretend that I'm pulling this data from some API
this.user = {
name: 'Fred',
email: 'fred#email.com',
address: '123 Fake St',
};
// Make a copy of the data for the purpose of assigning the
// default prop of each input
this.initialUser = _.cloneDeep(this.user);
},
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<template v-if="user">
<multi-line-input v-model="user.name" :default="initialUser.name"></multi-line-input>
<multi-line-input v-model="user.email" :default="initialUser.email"></multi-line-input>
<multi-line-input v-model="user.address" :default="initialUser.address"></multi-line-input>
</template>
</div>
Or, if you want the default value to be determined by the component instead of the parent (through a prop), you can do something like this instead:
Vue.component('multi-line-input', {
template: '<input #blur="onBlur" #input="onInput" :value="value">',
props: {
value: {
type: String,
default: '',
},
},
created() {
this.def = this.value;
},
methods: {
onBlur() {
if (!this.value && this.def) {
this.$emit('input', this.def);
}
},
onInput(e) {
this.$emit('input', e.target.value);
},
},
});
new Vue({
el: '#app',
data: {
user: null,
},
created() {
// Pretend that I'm pulling this data from some API
this.user = {
name: 'Fred',
email: 'fred#email.com',
address: '123 Fake St',
};
},
});
<script src="https://rawgit.com/vuejs/vue/dev/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.min.js"></script>
<div id="app">
<template v-if="user">
<multi-line-input v-model="user.name"></multi-line-input>
<multi-line-input v-model="user.email"></multi-line-input>
<multi-line-input v-model="user.address"></multi-line-input>
</template>
</div>
However I do not recommend the second approach because the child component instance will only every have one default value for its entire lifetime. Vue reuses component instances whenever possible, so it wouldn't work if Vue were to bind it to a different parent component (how/when would it update its own default state?).

Categories

Resources