Utilizing Vuetify's v-date-picker
in multiple components can result in code repetition. To address this, I decided to create a custom <custom-date-picker />
component that can be used wherever needed.
- This child component should emit the formatted date value to the parent.
- The parent component contains a button that logs the formatted date to the console.
https://i.sstatic.net/AxVM8.png
However, I encountered an error message with my current code:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "value"
found in
---> <CustomDatePicker> at components/CustomDatePicker.vue
<Pages/index.vue> at pages/index.vue
The parent component is located in pages/index.vue:
<template>
<div>
<custom-date-picker v-model="date" />
<v-btn @click="getDate">
Ok
</v-btn>
</div>
</template>
<script>
import CustomDatePicker from '@/components/CustomDatePicker.vue'
export default {
components: { CustomDatePicker },
data () {
return {
date: ''
}
},
methods: {
getDate () {
console.log(this.date)
}
}
}
</script>
The child component is located in components/CustomDatePicker.vue:
<template>
<v-container fill-height>
<v-row justify="center" align="center">
<v-col cols="12">
<!-- Date picker -->
<v-menu
ref="menu1"
v-model="menu1"
:close-on-content-click="false"
transition="scale-transition"
offset-y
>
<template v-slot:activator="{ on }">
<v-text-field
v-bind:value="value"
v-on:input="$emit('input', $event)"
@blur="date = parseDate(value)"
v-on="on"
value
label="Date"
color="green lighten-1"
/>
</template>
<v-date-picker
v-model="date"
@input="menu1 = false"
no-title
header-color="green lighten-1"
color="green lighten-1"
/>
</v-menu>
<!-- end of date picker -->
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'CustomDatePicker',
props: {
value: {
type: String,
default: ''
}
},
data () {
return {
menu1: null,
date: null
}
},
computed: {
computedDateFormatted () {
return this.formatDate(this.date)
}
},
watch: {
date (val) {
this.value = this.formatDate(this.date)
}
},
methods: {
formatDate (date) {
if (!date) { return null }
return date
},
parseDate (date) {
if (!date) { return null }
const [year, month, day] = date.split('-')
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
}
}
}
</script>
How can I resolve this issue?
A basic demo of this setup is available on Github if you wish to explore further :)
UPDATE 1:
I have managed to eliminate the previous error message by avoiding direct mutation of the prop value
. Now, I can select a date and upon clicking the Ok button it correctly logs the selected date. However, the text field in the parent component does not display the chosen date as illustrated in the image above.
Below is the updated code for the child component:
<template>
<v-container fill-height>
<v-row justify="center" align="center">
<v-col cols="12">
<!-- Date picker -->
<v-menu
ref="menu1"
v-model="menu1"
:close-on-content-click="false"
transition="scale-transition"
offset-y
>
<template v-slot:activator="{ on }">
<v-text-field
v-model="dateFormatted"
@blur="date = parseDate(dateFormatted)"
v-on="on"
value
label="Date"
color="green lighten-1"
/>
</template>
<v-date-picker
v-bind:value="value"
v-on:input="$emit('input', $event)"
@input="menu1 = false"
no-title
header-color="green lighten-1"
color="green lighten-1"
/>
</v-menu>
<!-- end of date picker -->
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'CustomDatePicker',
props: {
value: {
type: String,
default: ''
}
},
data () {
return {
menu1: null,
date: null,
dateFormatted: null
}
},
computed: {
computedDateFormatted () {
return this.formatDate(this.date)
}
},
watch: {
date (val) {
this.dateFormatted = this.formatDate(this.date)
}
},
methods: {
formatDate (date) {
if (!date) { return null }
return date
},
parseDate (date) {
if (!date) { return null }
const [year, month, day] = date.split('-')
return `${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}`
}
}
}
</script>