I’m using a custom Vue 3 input component, TheInput2, in a project, but the default values are not displaying. The streamData object contains the necessary data, but the input fields remain empty.
Here’s the TheInput2 (custom input) component:
<code><template>
<div class="the-input-wrapper">
<label v-if="label" :for="genId">
<span>{{ label }}</span>
</label>
<div class="input-container" :class="[ verification && 'input-container-verification' ]">
<slot name="before"></slot>
<input :id="genId"
v-maska
ref="inputElement"
v-model="inputModel"
:class="[ errorMessage && 'error', icon && 'ml-[30px]' ]"
:type="inputTypeResult"
:disabled="disabled"
:placeholder="placeholder"
@maska="setInputValue"
v-bind="{
...(mask && { 'data-maska': mask }),
...(name && { name })
}"
/>
<component v-if="icon" :is="icon"/>
<div v-if="type === 'password'" class="password-toggle">
<component :is="isShowPassword ? PasswordShowedIcon : PasswordHiddenIcon"
@click="isShowPassword = !isShowPassword" />
</div>
</div>
<div v-if="errorMessage" class="errors mb-2">{{ $t(errorMessage) || errorMessage }}</div>
<div v-else class="hint"><slot name="hint"></slot></div>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, computed, ref, onMounted, watch } from 'vue'
import lodash from 'lodash'
import { vMaska, MaskaDetail } from "maska"
import { useField } from 'vee-validate';
import PasswordHiddenIcon from '@/assets/icons/eye-slash.svg?component'
import PasswordShowedIcon from '@/assets/icons/eye.svg?component'
const isShowPassword = ref<boolean>(false)
const genId = ref<string>('input-' + lodash.random(1, 100000))
const inputElement = ref<HTMLInputElement>('')
const props = defineProps<{
value: string | number
label?: string
disabled?: boolean
placeholder?: string
mask?: string
unmaskedValue?: boolean
type?: 'email' | 'password' | 'string' | 'number'
name: string
verification?: boolean
icon?: object
}>()
const emits = defineEmits(['success-mask', 'update:value'])
const { value: validateValueModel, errorMessage, resetField } = useField(() => props.name)
const inputTypeResult = computed<'email' | 'password' | 'text'>(() => {
if (props.type === 'password') return isShowPassword.value ? 'text' : 'password'
return props.type || 'text'
})
const inputModel = computed({
get: () => props.value,
set: (newValue: string | number) => {
emits('update:value', newValue)
validateValueModel.value = newValue
}
})
const setInputValue = (maskEvent: CustomEvent<MaskaDetail>) => {
inputModel.value = props.unmaskedValue || !props.mask ? maskEvent.detail.unmasked : maskEvent.detail.masked
if (maskEvent.detail.completed) emits('success-mask')
}
watch(
() => inputModel.value,
(newValue) => {
if (!newValue) {
inputElement.value.value = props.mask ? ' ' : ''
resetField()
}
}
)
onMounted(() => {
inputElement.value.value = props.mask ? ' ' + inputModel.value : inputModel.value
})
</script>
<style scoped lang="scss">
.input-container {
/* styles... */
}
.input-container-verification {
/* styles... */
}
@keyframes pulse-border {
/* styles... */
}
.the-input-wrapper {
/* styles... */
}
</style>
</code>
<code><template>
<div class="the-input-wrapper">
<label v-if="label" :for="genId">
<span>{{ label }}</span>
</label>
<div class="input-container" :class="[ verification && 'input-container-verification' ]">
<slot name="before"></slot>
<input :id="genId"
v-maska
ref="inputElement"
v-model="inputModel"
:class="[ errorMessage && 'error', icon && 'ml-[30px]' ]"
:type="inputTypeResult"
:disabled="disabled"
:placeholder="placeholder"
@maska="setInputValue"
v-bind="{
...(mask && { 'data-maska': mask }),
...(name && { name })
}"
/>
<component v-if="icon" :is="icon"/>
<div v-if="type === 'password'" class="password-toggle">
<component :is="isShowPassword ? PasswordShowedIcon : PasswordHiddenIcon"
@click="isShowPassword = !isShowPassword" />
</div>
</div>
<div v-if="errorMessage" class="errors mb-2">{{ $t(errorMessage) || errorMessage }}</div>
<div v-else class="hint"><slot name="hint"></slot></div>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, computed, ref, onMounted, watch } from 'vue'
import lodash from 'lodash'
import { vMaska, MaskaDetail } from "maska"
import { useField } from 'vee-validate';
import PasswordHiddenIcon from '@/assets/icons/eye-slash.svg?component'
import PasswordShowedIcon from '@/assets/icons/eye.svg?component'
const isShowPassword = ref<boolean>(false)
const genId = ref<string>('input-' + lodash.random(1, 100000))
const inputElement = ref<HTMLInputElement>('')
const props = defineProps<{
value: string | number
label?: string
disabled?: boolean
placeholder?: string
mask?: string
unmaskedValue?: boolean
type?: 'email' | 'password' | 'string' | 'number'
name: string
verification?: boolean
icon?: object
}>()
const emits = defineEmits(['success-mask', 'update:value'])
const { value: validateValueModel, errorMessage, resetField } = useField(() => props.name)
const inputTypeResult = computed<'email' | 'password' | 'text'>(() => {
if (props.type === 'password') return isShowPassword.value ? 'text' : 'password'
return props.type || 'text'
})
const inputModel = computed({
get: () => props.value,
set: (newValue: string | number) => {
emits('update:value', newValue)
validateValueModel.value = newValue
}
})
const setInputValue = (maskEvent: CustomEvent<MaskaDetail>) => {
inputModel.value = props.unmaskedValue || !props.mask ? maskEvent.detail.unmasked : maskEvent.detail.masked
if (maskEvent.detail.completed) emits('success-mask')
}
watch(
() => inputModel.value,
(newValue) => {
if (!newValue) {
inputElement.value.value = props.mask ? ' ' : ''
resetField()
}
}
)
onMounted(() => {
inputElement.value.value = props.mask ? ' ' + inputModel.value : inputModel.value
})
</script>
<style scoped lang="scss">
.input-container {
/* styles... */
}
.input-container-verification {
/* styles... */
}
@keyframes pulse-border {
/* styles... */
}
.the-input-wrapper {
/* styles... */
}
</style>
</code>
<template>
<div class="the-input-wrapper">
<label v-if="label" :for="genId">
<span>{{ label }}</span>
</label>
<div class="input-container" :class="[ verification && 'input-container-verification' ]">
<slot name="before"></slot>
<input :id="genId"
v-maska
ref="inputElement"
v-model="inputModel"
:class="[ errorMessage && 'error', icon && 'ml-[30px]' ]"
:type="inputTypeResult"
:disabled="disabled"
:placeholder="placeholder"
@maska="setInputValue"
v-bind="{
...(mask && { 'data-maska': mask }),
...(name && { name })
}"
/>
<component v-if="icon" :is="icon"/>
<div v-if="type === 'password'" class="password-toggle">
<component :is="isShowPassword ? PasswordShowedIcon : PasswordHiddenIcon"
@click="isShowPassword = !isShowPassword" />
</div>
</div>
<div v-if="errorMessage" class="errors mb-2">{{ $t(errorMessage) || errorMessage }}</div>
<div v-else class="hint"><slot name="hint"></slot></div>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits, computed, ref, onMounted, watch } from 'vue'
import lodash from 'lodash'
import { vMaska, MaskaDetail } from "maska"
import { useField } from 'vee-validate';
import PasswordHiddenIcon from '@/assets/icons/eye-slash.svg?component'
import PasswordShowedIcon from '@/assets/icons/eye.svg?component'
const isShowPassword = ref<boolean>(false)
const genId = ref<string>('input-' + lodash.random(1, 100000))
const inputElement = ref<HTMLInputElement>('')
const props = defineProps<{
value: string | number
label?: string
disabled?: boolean
placeholder?: string
mask?: string
unmaskedValue?: boolean
type?: 'email' | 'password' | 'string' | 'number'
name: string
verification?: boolean
icon?: object
}>()
const emits = defineEmits(['success-mask', 'update:value'])
const { value: validateValueModel, errorMessage, resetField } = useField(() => props.name)
const inputTypeResult = computed<'email' | 'password' | 'text'>(() => {
if (props.type === 'password') return isShowPassword.value ? 'text' : 'password'
return props.type || 'text'
})
const inputModel = computed({
get: () => props.value,
set: (newValue: string | number) => {
emits('update:value', newValue)
validateValueModel.value = newValue
}
})
const setInputValue = (maskEvent: CustomEvent<MaskaDetail>) => {
inputModel.value = props.unmaskedValue || !props.mask ? maskEvent.detail.unmasked : maskEvent.detail.masked
if (maskEvent.detail.completed) emits('success-mask')
}
watch(
() => inputModel.value,
(newValue) => {
if (!newValue) {
inputElement.value.value = props.mask ? ' ' : ''
resetField()
}
}
)
onMounted(() => {
inputElement.value.value = props.mask ? ' ' + inputModel.value : inputModel.value
})
</script>
<style scoped lang="scss">
.input-container {
/* styles... */
}
.input-container-verification {
/* styles... */
}
@keyframes pulse-border {
/* styles... */
}
.the-input-wrapper {
/* styles... */
}
</style>
And here is the OwnerStreamForm component where I use TheInput2:
<code><template>
<div class="owner-stream-form-container">
<form @submit.prevent="handleSubmit" class="owner-stream-form">
<div class="form-group">
<div class="label-container">
<label for="streamTitle">{{ $t('form_labels.stream_title') }}</label>
<p class="field-description">{{ $t('form_descriptions.stream_title') }}</p>
</div>
<TheInput2
id="streamTitle"
v-model:value="streamData.title"
required
/>
</div>
<!-- other form groups -->
</form>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import TheButton from '@/components/settings/TheButton.vue';
import TheInput2 from '@/components/ui/form/TheInput2/index.vue';
import TheTextarea2 from '@/components/ui/form/TheTextarea2/index.vue';
import { useRoute } from 'vue-router';
import { useWowStream } from '@/composables/useWowStream';
const props = defineProps({
profile: {
type: Object,
required: true
}
});
const route = useRoute();
const { saveStreamData, isLoadingSubmitForm } = useWowStream();
const streamData = ref({
title: '',
description: '',
link: '',
lobbyNumber: '',
lobbyPassword: '',
prize: ''
});
const handleSubmit = async () => {
await saveStreamData(route.params.id, streamData.value);
};
onMounted(() => {
const stream = props.profile.wow_streamer?.wow_streams[0];
if (stream) {
streamData.value = {
title: stream.name,
description: stream.description,
link: stream.link,
lobbyNumber: stream.data.lobby_number,
lobbyPassword: stream.data.password,
prize: stream.data.prize
};
}
});
</script>
<style scoped lang="scss">
.owner-stream-form-container {
/* styles... */
}
.owner-stream-form {
/* styles... */
}
.stream-preview {
/* styles... */
}
</style>
</code>
<code><template>
<div class="owner-stream-form-container">
<form @submit.prevent="handleSubmit" class="owner-stream-form">
<div class="form-group">
<div class="label-container">
<label for="streamTitle">{{ $t('form_labels.stream_title') }}</label>
<p class="field-description">{{ $t('form_descriptions.stream_title') }}</p>
</div>
<TheInput2
id="streamTitle"
v-model:value="streamData.title"
required
/>
</div>
<!-- other form groups -->
</form>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import TheButton from '@/components/settings/TheButton.vue';
import TheInput2 from '@/components/ui/form/TheInput2/index.vue';
import TheTextarea2 from '@/components/ui/form/TheTextarea2/index.vue';
import { useRoute } from 'vue-router';
import { useWowStream } from '@/composables/useWowStream';
const props = defineProps({
profile: {
type: Object,
required: true
}
});
const route = useRoute();
const { saveStreamData, isLoadingSubmitForm } = useWowStream();
const streamData = ref({
title: '',
description: '',
link: '',
lobbyNumber: '',
lobbyPassword: '',
prize: ''
});
const handleSubmit = async () => {
await saveStreamData(route.params.id, streamData.value);
};
onMounted(() => {
const stream = props.profile.wow_streamer?.wow_streams[0];
if (stream) {
streamData.value = {
title: stream.name,
description: stream.description,
link: stream.link,
lobbyNumber: stream.data.lobby_number,
lobbyPassword: stream.data.password,
prize: stream.data.prize
};
}
});
</script>
<style scoped lang="scss">
.owner-stream-form-container {
/* styles... */
}
.owner-stream-form {
/* styles... */
}
.stream-preview {
/* styles... */
}
</style>
</code>
<template>
<div class="owner-stream-form-container">
<form @submit.prevent="handleSubmit" class="owner-stream-form">
<div class="form-group">
<div class="label-container">
<label for="streamTitle">{{ $t('form_labels.stream_title') }}</label>
<p class="field-description">{{ $t('form_descriptions.stream_title') }}</p>
</div>
<TheInput2
id="streamTitle"
v-model:value="streamData.title"
required
/>
</div>
<!-- other form groups -->
</form>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import TheButton from '@/components/settings/TheButton.vue';
import TheInput2 from '@/components/ui/form/TheInput2/index.vue';
import TheTextarea2 from '@/components/ui/form/TheTextarea2/index.vue';
import { useRoute } from 'vue-router';
import { useWowStream } from '@/composables/useWowStream';
const props = defineProps({
profile: {
type: Object,
required: true
}
});
const route = useRoute();
const { saveStreamData, isLoadingSubmitForm } = useWowStream();
const streamData = ref({
title: '',
description: '',
link: '',
lobbyNumber: '',
lobbyPassword: '',
prize: ''
});
const handleSubmit = async () => {
await saveStreamData(route.params.id, streamData.value);
};
onMounted(() => {
const stream = props.profile.wow_streamer?.wow_streams[0];
if (stream) {
streamData.value = {
title: stream.name,
description: stream.description,
link: stream.link,
lobbyNumber: stream.data.lobby_number,
lobbyPassword: stream.data.password,
prize: stream.data.prize
};
}
});
</script>
<style scoped lang="scss">
.owner-stream-form-container {
/* styles... */
}
.owner-stream-form {
/* styles... */
}
.stream-preview {
/* styles... */
}
</style>
streamData has data, but it does not display in TheInput2 component. What am I missing here?