I have an input in a child component and when the user starts typing in the input it updates the data in the parent component, or should. This is my code, I can post other parts if useful.
Child
<input
:keyup="updateFilters(filters[data.field.key])"
:placeholder="data.label"
/>
methods: {
updateFilters(value) {
this.$emit("input", value);
}
}
Parent
data() {
return {
filters: {
name: "",
age: "",
address: "",
},
};
},
You can change the parent from child component the same way you do emiting other events like onchange, onkeyup, onkeydown etc.
Vue.component('parent', {
data() {
return {
parentValue: ''
};
},
template: `
<div>
<label>Parent state value:</label>
{{parentValue}}
<br/><br/>
<label>input is the child component:</label>
<br/>
<child #fromChild="fromChild"></child>
</div>
`,
methods: {
fromChild(value) {
this.parentValue = value
console.log(value) // someValue
}
}
})
Vue.component('child', {
template: `
<input
v-on:keyup="updateParent($event.target.value)"
placeholder="type something"
/>
`,
methods: {
updateParent(value) {
console.log(value)
this.$emit("fromChild", value);
}
},
})
new Vue({
el: "#app",
data: {
label: 'in Vue'
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
}
}
})
I've prepared a working example here.
Related
I'm trying to create custom Input component with composition API in Vue 3 but when I'm trying to update value with v-model I am getting empty string instead of event value and when I replace custom Input component with default HTML input the value is being updated as expected
Input component:
<template>
<input
:type="type"
:id="name"
:name="name"
:placeholder="placeholder"
class="input"
v-model="modelValue"
/>
</template>
<script lang="ts">
import { computed } from 'vue';
export default {
name: 'Input',
props: {
modelValue: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
type: {
type: String,
default: 'text',
},
placeholder: {
type: String,
required: true,
},
},
setup(props: { value: string }, { emit }) {
const modelValue = computed({
get() {
return props.modelValue;
},
set(value) {
emit('input', value);
},
});
return { modelValue };
},
};
</script>
<form #submit.prevent="handleSubmit">
<Input :name="name" placeholder="Name*" v-model="name" />
<Button>Send</Button>
</form>
Setup method:
setup() {
const name = ref('');
function handleSubmit(data: Event) {
console.log(data, name.value);
}
watch(name, (old, newValue) => {
console.log(name, old, newValue);
});
return { name, handleSubmit };
},
There are a couple of errors & warnings in your code:
you should declare emitted events in the emits option (more on this here)
you did not have a value prop passed down from the parent component to Input (thus I removed it)
if you want to do the "easy sync" with v-model, then it's best to emit a custom event called update:modelValue (where modelValue is the value you want to bind as prop, e.g. update:name; more on this here)
you should NOT name a variable in the setup function the same as a prop (this is just sensibility - you'll mess up what is what, eventually)
const {
computed,
ref,
} = Vue
const Input = {
name: 'Input',
props: {
name: {
type: String,
required: true,
},
type: {
type: String,
default: 'text',
},
placeholder: {
type: String,
required: true,
},
},
emits: ['update:name'],
setup(props, { emit }) {
const modelValue = computed({
get() {
return props.name;
},
set(value) {
emit('update:name', value);
},
});
return {
modelValue
};
},
template: `
<input
:type="type"
:id="name"
:name="name"
:placeholder="placeholder"
class="input"
v-model="modelValue"
/>
`
}
const App = {
setup() {
const name = ref('');
function handleSubmit(data) {
console.log(data, name.value);
}
return {
name,
handleSubmit
};
},
template: `
Name Ref: {{ name }}<br />
<form #submit.prevent="handleSubmit">
<Input
:name="name"
placeholder="Name*"
v-model="name"
/>
<button type="submit">Send</button>
</form>
`
}
const vm = Vue.createApp(App)
vm.component('Input', Input)
vm.mount('#app')
<script src="https://unpkg.com/vue#next"></script>
<div id="app"></div>
I'd like to have a list which maps the modified values back to the description in the container. In other way, I want the container to have a prop shortcuts like:
[{action: "foo"}]
with text being editable in the component created from the list.
I've got the container:
Vue.component("shortcuts", {
data: function () {
return {
shortcuts: []
}
},
methods: {
add: function() {
this.shortcuts.push({
action: "",
})
},
},
template: '<div>\
<shortcut-entry v-for="(shortcut, index) in shortcuts" is="shortcut-entry" v-bind:key="index" v-bind="shortcut" #remove="shortcuts.splice(index, 1)"></shortcut-entry>\
<br>\
<button v-on:click=add>Add</button>\
</div>'
})
And the list element:
Vue.component("shortcut-entry", {
methods: {
clearParams: function() {
this.params = {}
},
remove: function() {
this.$emit("remove")
}
},
props: {
action: String,
},
template: '<div>\
<input type="text" v-model="action"></input>\
<button v-on:click="remove">Remove</button>\
</div>'
})
However this results in a warning:
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders.
But if I use data instead of props in the shortcut-entry, the changes do not propagate into the objects in the list either.
What am I missing here? How can I bind the text entry in the item back to the object in the list in the container?
Use v-model to update the state:
Vue.component('shortcuts', {
props: {
value: Array // Of shortcuts
},
methods: {
add: function () {
const newValue = [...this.value, { action: '' }]
this.$emit('input', newValue)
},
remove: function (index) {
const newValue = [...this.value]
newValue.splice(index, 1)
this.$emit('input', newValue)
}
},
template: '<div>\
<shortcut-entry v-for="(shortcut, index) in value" is="shortcut-entry" v-bind:key="index" v-bind="shortcut" #remove="remove(index)"></shortcut-entry>\
<br>\
<button v-on:click=add>Add</button>\
</div>'
And the parent component is something like this:
Vue.component('parent-comp', {
data () {
return {
shortcuts: []
}
},
template: '<shortcut v-model="shortcuts"></shortcut>'
})
*** Use .sync ***
Vue.component('shortcuts', {
props: {
shortcuts: Array // Of shortcuts
},
methods: {
add: function () {
const newValue = [...this.shortcuts, { action: '' }]
this.$emit('update:shortcuts', newValue)
},
remove: function (index) {
const newValue = [...this.shortcuts]
newValue.splice(index, 1)
this.$emit('update:shortcuts', newValue)
}
},
template: '<div>\
<shortcut-entry v-for="(shortcut, index) in shortcuts" is="shortcut-entry" v-bind:key="index" v-bind="shortcut" #remove="remove(index)"></shortcut-entry>\
<br>\
<button v-on:click=add>Add</button>\
</div>'
})
So, the caller component:
Vue.component('parent-comp', {
data () {
return {
shortcuts: []
}
},
template: '<shortcut :shortcuts.sync="shortcuts"></shortcut>'
})
I did a custom input component, it works correctly but there is a problem: when i try to update its value from a method the model is updated but the input value still there.
This is my component:
https://codepen.io/ken-ramirez/pen/JgyKad
const BasicInput = {
template: '<input v-model="content" #input="handleInput" />',
prop: ['value'],
data () {
return {
content: this.value
}
},
methods: {
handleInput (e) {
this.$emit('input', this.content)
}
}
}
new Vue({
el: '#app',
data: { name: '' },
components: { BasicInput },
methods: {
restart() {
this.name = ''
}
}
})
You can press on restart button to see what i mean.
You have a mistake in your code: props, not prop.
But this is not enough, also you need to update your content manually, it's not reactive with value prop. Everything declared inside data is not reactive with its initial values.
const BasicInput = {
template: '<input v-model="content" #input="handleInput" />',
props: ['value'],
data () {
return {
content: this.value
}
},
methods: {
handleInput (e) {
this.$emit('input', this.content)
}
},
watch: {
value(val) {
this.content = val;
}
}
}
Like #NikitaK mentioned in his answer you're making a typo , you should write props instead of prop, but i want to give a shorter solution without using watcher property only with this code #input="$emit('input',$event.target.value)"
Full example
const BasicInput = {
template: `<input :value="value" #input="$emit('input',$event.target.value)" />`,
props: ['value']
}
new Vue({
el: '#app',
data() {
return{
name: ''
}},
components: { BasicInput },
methods: {
restart() {
this.name = ''
}
}
})
body {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<basic-input v-model="name"></basic-input>
<p>
<strong>Name:</strong> {{ name }}
</p>
<button #click="restart">restart</button>
</div>
I'm currently using this UI component from http://www.vue-tags-input.com
I'm planning to create a reusable component for vue-tags-input, here's my current code:
components/UI/BaseInputTag.vue
<template>
<b-form-group :label="label">
<no-ssr>
<vue-tags-input
:value="tags"
#tags-changed="updateValue"/>
</no-ssr>
</b-form-group>
</template>
<script>
export default {
name: 'BaseInputTag',
props: {
label: { type: String, required: true },
value: { type: [String, Number, Array] },
tags: { type: [Array] }
},
methods: {
updateValue(newTags) {
this.$emit('input', newTags);
}
}
}
</script>
and in my parent vue page. I'm calling above component with this code:
pages/users/new.vue
<BaseInputTag v-model="tag" :tags="interests" label="Interests"/>
<script>
export default {
name: 'InsiderForm',
data() {
return {
tag: '',
interests: []
};
}
}
</script>
How can I emit back the child component's newTags to parent's data interests
You're almost there!
Parent component:
<BaseInputTag v-model="tag" :tags="interests" #input="doStuffWithChildValue" label="Interests"/>
<script>
export default {
name: 'InsiderForm',
data() {
return {
tag: '',
interests: []
};
},
methods: {
doStuffWithChildValue (value) {
console.log('Got value from child', value)
}
}
}
</script>
I managed to make it work, here's my code:
child-component
<template>
<b-form-group :label="label">
<no-ssr>
<vue-tags-input
:value="value"
v-model="tag"
placeholder="Add Tag"
:tags="tags"
#tags-changed="updateValue"/>
</no-ssr>
</b-form-group>
</template>
<script>
export default {
name: 'BaseInputTag',
props: {
label: { type: String, required: true },
value: { type: [String, Number, Array] },
tags: { type: [Array, String] },
validations: { type: Object, required: true }
},
data() {
return {
tag: ''
}
},
methods: {
updateValue(newTags) {
this.$emit('updateTags', newTags);
}
}
}
</script>
and to receive the changes to parent component:
<BaseInputTag :tags="interests" #updateTags="interests = $event" label="Interests"/>
<script>
export default {
name: 'InsiderForm',
data() {
return {
tag: '',
interests: []
};
}
}
</script>
How to binding parent's model to child in Vue.js?
These codes below is works fine. if i fill the input manually, then child's model return it's value to the parent's model.
But the issue is, if the data set from AJAX request in a parent, the input doesn't automatically filled.
Can anyone help me on this?
Form.vue
<template>
<form-input v-model="o.name" :fieldModel="o.name" #listenChanges="o.name = $event"/>
<form-input v-model="o.address" :fieldModel="o.address" #listenChanges="o.address = $event"/>
</template>
<script>
import FormInput from '../share/FormInput.vue'
export default {
data () {
return {
o: {
name: '',
address: ''
}
}
},
components: { 'form-input': FormInput },
created: function() {
axios.get('http://api.example.com')
.then(response => {
this.o.name = response.data.name
this.o.address = response.data.address
})
.catch(e => { console.log(e) })
}
}
</script>
FormInput.vue
<template>
<input type="text" v-model='fieldModelValue' #input="forceUpper($event, fieldModel)">
</template>
<script>
export default {
props: ['fieldModel'],
data() {
return {
fieldModelValue: ''
}
},
mounted: function() {
this.fieldModelValue = this.fieldModel;
},
methods: {
forceUpper(e, m) {
const start = e.target.selectionStart;
e.target.value = e.target.value.toUpperCase();
this.fieldModelValue = e.target.value.toUpperCase();
this.$emit('listenChanges', this.fieldModelValue)
}
}
}
</script>
Things are more straightforward if you take advantage of v-model in components.
If you put v-model on a component, the component should take a prop named value, and should emit input events to trigger it to update.
I like to make a computed to hide the event emitting, and allow me to just v-model the computed inside my component.
new Vue({
el: '#app',
data: {
o: {
name: '',
address: ''
}
},
components: {
'form-input': {
template: '#form-input',
props: ['value'],
computed: {
fieldModelValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit('input', newValue.toUpperCase());
}
}
}
}
},
// Simulate axios call
created: function() {
setTimeout(() => {
this.o.name = 'the name';
this.o.address = 'and address';
}, 500);
}
});
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
Name ({{o.name}})
<form-input v-model="o.name"></form-input>
Address ({{o.address}})
<form-input v-model="o.address"></form-input>
</div>
<template id="form-input">
<input type="text" v-model='fieldModelValue'>
</template>
The mounted() hook is blocking subsequent updates from the parent.
Remove mounted and change v-model to 'fieldModel'
<template>
<input type="text" :value='fieldModel' #input="forceUpper($event, fieldModel)">
</template>
<script>
export default {
props: ['fieldModel'],
data() {
return {
fieldModelValue: ''
}
},
// mounted: function() {
// this.fieldModelValue = this.fieldModel;
// },
methods: {
forceUpper(e, m) {
const start = e.target.selectionStart;
e.target.value = e.target.value.toUpperCase();
this.fieldModelValue = e.target.value.toUpperCase();
this.$emit('listenChanges', this.fieldModelValue)
}
}
}
</script>
Demo CodeSandbox