Here's sample of my child component
HTML:
<div v-for="(input, index) in form.inputs" :key="index">
<div>
<input :name"input.name" :type="input.type" />
</div>
</div>
JavaScript (Vue):
<script>
export default {
name: "child",
props: ['parentForm'],
data() {
return {
form: {
inputs: [
{
name: 'name',
type: 'text'
],
[...]
}
}
}
And sample of root component
HTML:
<child :parentsForm="form"></child>
JavaScript (Vue):
<script>
import child from "./child";
export default {
name: "root",
components: { child },
data() {
return {
form: {
data: {
name: null,
email: null,
...
}
}
}
The question is, how do I achieve combining root + v-for?
Example I want to using child component this way
<input :name"input.name" :type="input.type" v-model="parentForm.data . input.name" />
Since parentForm.data will bind form:data:{ and this will be the variable get from input.name }
Output in v-model should be bind form.data.name or form.data.email on root component
Thank you
You can use it as per follow,
<input :name="input.name" :type="input.type" v-model="parentForm.data[input.name]" />
This will bind parentForm.data.name for input.name = 'name' to v-model.
If I understood you correctly, you want to update parent data from your child component. If yes then you have two options.
In you child component use $parent.form.data to bind.
Or you can pass it down as prop assign it to a data property in child. Bind this new data property in your child and emit it whenever any changes are made. Receive this emit in your parent and update the parent property respectively (Recommended)
Related
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" ... />
Right now I'm passing a trigger prop from the parent to child component, which triggers the emit from the child to the parent.
parent component:
<form #submit.prevent="state.store=true" method="post" enctype="multipart/form-data">
<child-component :triggerEmit=state.store #emitSomething="getSomething()"/>
child component:
const emit = defineEmits([
'emitBody'
])
watchEffect(async () => {
if (props.triggerEmit) {
emit('emitSomething', value)
}
})
This gets confusing quickly, if the components grow in size and I was wondering if there is a simpler way to trigger child emits from the parent, since this seems to be a common use case.
Edit:
Trying to trigger the child method directly from the parent (not working).
child:
const childMethod = () => {
console.log('check')
}
parent:
html:
<child ref="childRef"/>
script setup:
const childRef = ref()
childRef.value.childMethod()
Page throws error:
Cannot read properties of undefined (reading 'childMethod')
As per my understanding you want to access multiple child component methods/properties from the parent component. If Yes, you can achieve that by create a ref and access the methods.
In template :
<!-- parent.vue -->
<template>
<button #click="$refs.childComponentRef.childComponentMethod()">Click me</button>
<child-component ref="childComponentRef" />
</template>
In script :
With Vue 2 :
this.$refs.childComponentRef.childComponentMethod( );
With Vue 3 Composition Api :
setup( )
{
const childComponentRef = ref( );
childComponentRef.value.childComponentMethod( )
return {
childComponentRef
}
}
In this case, the parent's trigger is effectively querying the child for its event data so that it could call getSomething() on it. The parent already owns getSomething(), so it really only needs the child data.
Another way to get that data is to use v-model to track the child data:
In the child component, implement v-model for a prop (a string for example) by declaring a modelValue prop and emitting an 'update:modelValue' event with the new value as the event data:
<!-- ChildName.vue -->
<script setup>
defineProps({ modelValue: String })
defineEmits(['update:modelValue'])
</script>
<template>
<label>Name
<input type="text" :value="modelValue" #input="$emit('update:modelValue', $event.target.value)">
</label>
</template>
In the parent, add a reactive object, containing a field for each child's v-model:
<!-- ParentForm.vue -->
<script setup>
import { reactive } from 'vue'
const formData = reactive({
name: '',
age: 0,
address: {
city: '',
state: '',
},
})
</script>
<template>
<form>
<child-name v-model="formData.name" />
<child-age v-model="formData.age" />
<child-address v-model:city="formData.address.city" v-model:state="formData.address.state" />
<button>Submit</button>
</form>
</template>
Now, the parent can call getSomething() on each field upon submitting the form:
<!-- ParentForm.vue -->
<script setup>
import { toRaw } from 'vue'
⋮
const getSomething = field => {
console.log('getting', field)
}
const submit = () => {
Object.entries(toRaw(formData)).forEach(getSomething)
}
</script>
<template>
<form #submit.prevent="submit">
⋮
</form>
</template>
demo
I want to transmit the data from the input element of the child comp to the parent comp data property
using the$emit method the data can be transmited to the parent
directly binding the transmited property on the child property
using v-bind <input v-model="userinput" />
results in a Warning
runtime-core.esm-bundler.js?5c40:6870 [Vue warn]: Extraneous non-emits event listeners (transmitData) were passed to component but could not be automatically inherited because component renders fragment or text root nodes. If the listener is intended to be a component custom event listener only, declare it using the "emits" option.
at <UserInput onTransmitData=fn<bound receaveData> >
at <App>
what is the correct way to do this kind of operation ?
\\ child
<template>
<input v-model="userinput" />
<button type="button" #click="transmitData">transmit</button>
</template>
<script>
export default {
data() {
return {
userinput: " dd",
}
},
methods: {
transmitData(event){
this.$emit('transmit-data', this.userinput)
}
}
}
</script>
\\ parent
<template>
<user-input #transmit-data="receaveData" />
<p>{{ childData }}</p>
</template>
<script>
import UserInput from "./components/UserInput.vue";
export default {
name: "App",
components: {
UserInput,
},
data() {
return {
childData: " "
};
},
methods: {
receaveData(compTransmit){
console.log(compTransmit)
this.childData = compTransmit
},
},
};
</script>
you have to specify a emits option in the child component
docs
so in your case should look like this
export default {
emits: ['transmit-data'], // you need to add this :)
data() {
return { userinput: " dd" }
},
methods: {
transmitData(event){
this.$emit('transmit-data', this.userinput)
}
}
}
I have a question why not this component, not re-rendering after changing value so what I'm trying to do is a dynamic filter like amazon using the only checkboxes so let's see
I have 4 components [ App.vue, test-filter.vue, filtersInputs.vue, checkboxs.vue]
Here is code sandbox for my example please check the console you will see the value changing https://codesandbox.io/s/thirsty-varahamihira-nhgex?file=/src/test-filter/index.vue
the first component is App.vue;
<template>
<div id="app">
<h1>Filter</h1>
{{ test }}
<test-filter :filters="filters" :value="test"></test-filter>
</div>
</template>
<script>
import testFilter from "./test-filter";
import filters from "./filters";
export default {
name: "App",
components: {
testFilter,
},
data() {
return {
filters: filters,
test: {},
};
},
};
</script>
so App.vue that holds the filter component and the test value that I want to display and the filters data is dummy data that hold array of objects.
in my test-filter component, I loop throw the filters props and the filterInputs component output the input I want in this case the checkboxes.
test-filter.vue
<template>
<div class="test-filter">
<div
class="test-filter__filter-holder"
v-for="filter in filters"
:key="filter.id"
>
<p class="test-filter__title">
{{ filter.title }}
</p>
<filter-inputs
:value="value"
:filterType="filter.filter_type"
:options="filter.options"
#checkboxChanged="checkboxChanged"
></filter-inputs>
</div>
</div>
<template>
<script>
import filterInputs from "./filterInputs";
export default {
name: "test-filter",
components: {
filterInputs,
},
props:{
filters: {
type: Array,
default: () => [],
},
value: {
type: Array,
default: () => ({}),
},
},
methods:{
checkboxChanged(value){
// Check if there is a array in checkbox key if not asssign an new array.
if (!this.value.checkbox) {
this.value.checkbox = [];
}
this.value.checkbox.push(value)
}
};
</script>
so I need to understand why changing the props value also change to the parent component and in this case the App.vue and I tried to emit the value to the App.vue also the component didn't re-render but if I check the vue dev tool I see the value changed but not in the DOM in {{ test }}.
so I will not be boring you with more code the filterInputs.vue holds child component called checkboxes and from that, I emit the value of selected checkbox from the checkboxes.vue to the filterInputs.vue to the test-filter.vue and every component has the value as props and that it if you want to take a look the rest of components I will be glad if you Did.
filterInpust.vue
<template>
<div>
<check-box
v-if="filterType == checkboxName"
:options="options"
:value="value"
#checkboxChanged="checkboxChanged"
></check-box>
</div>
</template>
<script>
export default {
props: {
value: {
type: Object,
default: () => ({}),
},
options: {
type: Array,
default: () => [],
},
methods: {
checkboxChanged(value) {
this.$emit("checkboxChanged", value);
},
},
}
</script>
checkboxes.vue
<template>
<div>
<div
v-for="checkbox in options"
:key="checkbox.id"
>
<input
type="checkbox"
:id="`id_${_uid}${checkbox.id}`"
#change="checkboxChange"
:value="checkbox"
/>
<label
:for="`id_${_uid}${checkbox.id}`"
>
{{ checkbox.title }}
</label>
</div>
</div>
<template>
<script>
export default {
props: {
value: {
type: Object,
default: () => ({}),
},
options: {
type: Array,
default: () => [],
},
}
methods: {
checkboxChange(event) {
this.$emit("checkboxChanged", event.target.value);
},
},
};
</script>
I found the solution As I said in the comments the problem was that I'm not using v-model in my checkbox input Vue is a really great framework the problem wasn't in the depth, I test the v-model in my checkbox input and I found it re-render the component after I select any checkbox so I search more and found this article and inside of it explained how we can implement v-model in the custom component so that was the solution to my problem and also I update my codeSandbox Example if you want to check it out.
Big Thaks to all who supported me to found the solution: sarkiroka, Jakub A Suplick
Form (parent) component:
export default {
template: `
<form name="form" class="c form" v-on:change="onChange">
<slot></slot>
</form>`,
methods: {
onChange:function(){
console.log("something changed");
}
}
};
and a c-tool-bar component (child) that is (if existing) inside the c-form's slot
export default {
template: `
<div class="c tool bar m006">
<div v-if="changed">
{{ changedHint }}
</div>
<!-- some other irrelevant stuff -->
</div>`,
props: {
changed: {
type: Boolean,
default: false,
},
changedHint: String
}
};
What I want to achieve is, that when onChange of c-form gets fired the function
checks if the child c-tool-bar is present && it has a changedHint text declared (from backend) it should change the c-tool-bar's prop "changed" to true and the c-bar-tool updates itself so the v-if="changed" actually displays the changedHint. I read the vue.js docs but I don't really know where to start and what the right approach would be.
You can use the two components like this:
<parent-component></parent-component>
You will be passing in the :changed prop a variable defined in the parent component in data(). Note the :, we are using dynamic props, so once the prop value is changed in the parent it will update the child as well.
To change the display of the hint, change the value of the variable being passed in as prop to the child component:
export default {
template: `
<form name="form" class="c form" v-on:change="onChange">
<child-component :changed="shouldDisplayHint"></child-component>
</form>`,
data() {
return {
shouldDisplayHint: false,
};
},
methods: {
onChange:function(){
this.shouldDisplayHint = true; // change the value accordingly
console.log("something changed");
}
}
};
One simple way is to use $refs.
When you will create your child component inside your parent component, you should have something like: <c-tool-bar ref="child"> and then you can use it in your parent component code:
methods: {
onChange:function(){
this.$refs.child.(do whatever you want)
}
}
Hope it helps but in case of any more questions: https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs