I have read several posts on Vue 3 regarding v-model
and :value
.
But none of those really helped me in this case.
I want to store value in modelValue
prop and wanna update it whenever it gets updated or inputted by user.
This code runs without any errors but shows modelValue: undefined
in Vue 3 Dev Console.
My Vue code:
<template>
<span :class="wrapperClass">
<i :class="iconLeft" v-if="iconLeft" />
<InputText
:type="type"
:value="modelValue"
:class="{ 'p-filled': filled }"
@input="onInput"
/>
<label for="username">{{ label }}</label>
<i :class="iconRight" v-if="iconRight" />
</span>
</template>
<script lang="ts">
import InputText from "primevue/inputtext"
import { defineComponent } from "vue"
export default defineComponent({
name: "FormInput",
emits: ["update:modelValue"],
components: { InputText },
props: {
iconLeft: {
type: String,
default: "",
},
iconRight: {
type: String,
default: "",
},
error: {
type: String,
default: "",
},
label: {
type: String,
default: "",
},
type: {
type: String,
},
modelValue: {
type: String,
},
},
computed: {
wrapperClass(): any {
return {
"p-float-label": true,
"p-input-icon-left": this.iconLeft,
"p-input-icon-right": this.iconRight,
}
},
filled(): any {
return this.modelValue != null && this.modelValue.toString().length > 0
},
},
methods: {
onInput(event: any): any {
this.$emit("update:modelValue", event.target.value)
},
},
})
</script>
Parent Component:
<template>
<div class="home">
<Example :example="{ heading: 'fii' }" />
<Button label="Test" />
<hr />
<img alt="Vue logo" src="../assets/logo.png" />
<hr />
<FormInput v-bind="args" />
<hr />
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue"
import HelloWorld from "../components/HelloWorld.vue" // @ is an alias to /src
import Example from "../components/Example/Example.vue" // @ is an alias to /src
import FormInput from "../components/Form/FormInput.vue"
export default defineComponent({
name: "Home",
components: {
HelloWorld,
Example,
FormInput,
},
setup() {
return {
args: {
label: "test",
type: "text",
value: "",
},
}
},
})
</script>
Please see this screenshot
Parent Component in Dev Console
There are few things to fix:
PrimeVue component InputText
has no prop value
but modelValue
(to support v-model
) - InputText Docs + read Using v-model on Components to learn a difference between v-model
on native inputs vs custom components
InputText
is not a native input
element, so there is no point to listen for a input
event. The component is emitting update:modelValue
event. Also event payload is not native event so event.target.value
makes no sense, just use value
- source
There is no point in duplicating the p-filled
as InputText
does this already (see source above)
// FormInput.vue
<template>
<span :class="wrapperClass">
<i :class="iconLeft" v-if="iconLeft" />
<InputText
:type="type"
:modelValue="modelValue"
@update:model-value="onInput"
/>
<label for="username">{{ label }}</label>
<i :class="iconRight" v-if="iconRight" />
</span>
</template>
<script lang="ts">
import InputText from "primevue/inputtext"
import { defineComponent } from "vue"
export default defineComponent({
name: "FormInput",
emits: ["update:modelValue"],
components: { InputText },
props: {
iconLeft: {
type: String,
default: "",
},
iconRight: {
type: String,
default: "",
},
error: {
type: String,
default: "",
},
label: {
type: String,
default: "",
},
type: {
type: String,
default: "text"
},
modelValue: {
type: String,
required: true
},
},
computed: {
wrapperClass(): any {
return {
"p-float-label": true,
"p-input-icon-left": this.iconLeft,
"p-input-icon-right": this.iconRight,
}
},
},
methods: {
onInput(value: any): any {
this.$emit("update:modelValue", value)
},
},
})
</script>
Usage:
<FormInput v-model="data" />
<!-- OR -->
<FormInput :modelValue="data" @update:model-value="data = $event" />
Demo:
const app = Vue.createApp({
data() {
return {
text: "default value",
}
},
});
app.component('FormInput', {
emits: ["update:modelValue"],
components: {
'inputtext': primevue.inputtext
},
template: `
<span :class="wrapperClass">
<i :class="iconLeft" v-if="iconLeft" />
<inputtext
:type="type"
:modelValue="modelValue"
@update:model-value="onInput"
></inputtext>
<label for="username">{{ label }}</label>
<i :class="iconRight" v-if="iconRight" />
</span>
`,
props: {
iconLeft: {
type: String,
default: "",
},
iconRight: {
type: String,
default: "",
},
error: {
type: String,
default: "",
},
label: {
type: String,
default: "",
},
type: {
type: String,
default: "text"
},
modelValue: {
type: String,
required: true
},
},
computed: {
wrapperClass() {
return {
"p-float-label": true,
"p-input-icon-left": this.iconLeft,
"p-input-icon-right": this.iconRight,
}
},
},
methods: {
onInput(value) {
this.$emit("update:modelValue", value)
},
},
});
app.mount('#app');
<link href="https://unpkg.com/primevue/resources/themes/saga-blue/theme.css" rel="stylesheet">
<link href="https://unpkg.com/primevue/resources/primevue.min.css" rel="stylesheet">
<link href="https://unpkg.com/primeicons/primeicons.css" rel="stylesheet">
<script src="https://unpkg.com/vue@next"></script>
<script src="https://unpkg.com/primevue/inputtext/inputtext.min.js"></script>
<div id="app">
<form-input v-model="text"></form-input>
<pre>
{{ text }}
</pre>
</div>