Reputation: 39
I have an app with a custom input component where I'm trying to emit the change events up to the parent on save and send those values to the $store via commit/dispatch. I got confirmation that the parent can receive the values when I was debugging the component earlier (was retrieving the same values originally generated from the randomizer in this app, and confused as to why I'm getting this error:
[Vue warn]: Error in v-on handler: "TypeError: Cannot read properties of undefined (reading 'value')"
found in
---> <CustomInput>
It only shows up when a user is trying to type a new title/subtitle when editing them manually and shows up after each keystroke. Is this a timing error??
Custom Input:
<template>
<div>
<label for="title">Edit Title: </label>
<input
type="text"
id="title"
:updateTitle="updateTitle"
v-model="inputTitle"
/>
<label for="title">Edit Subtitle: </label>
<input
type="text"
id="subtitle"
:updateSubtitle="updateSubtitle"
v-model="inputSubtitle"
/>
</div>
</template>
<script>
export default {
name: 'CustomInput',
props: {
value: {
type: Object,
required: true,
},
},
computed: {
updateTitle() {
console.log('updateTitle: ', this.value.title);
return this.value.title;
},
updateSubtitle() {
console.log('updateSubtitle: ', this.value.subtitle);
return this.value.subtitle;
},
inputTitle: {
get() {
return this.value.title;
},
set(title) {
console.log('setting new title: ', title);
this.$emit('input', title);
},
},
inputSubtitle: {
get() {
return this.value.subtitle;
},
set(subtitle) {
console.log('setting new subtitle: ', subtitle);
this.$emit('input', subtitle);
},
},
},
};
</script>
Parent:
<template>
<main class="home-page page">
<div v-if="!editMode" class="display-information">
<div class="title">
<span class="bold">Title: </span>{{title}}
</div>
<div class="subtitle">
<span class="bold">Subtitle: </span>{{subtitle}}
</div>
<div class="controls">
<button id="randomize-button" class="control-button" @click="randomizeTitleAndSubtitle">
Randomize
</button>
<button id="edit-button" class="control-button" @click="onEdit">Edit</button>
</div>
</div>
<div v-else class="edit-controls">
<CustomInput
:value="{ title, subtitle }"
@input="onSave(v = { title, subtitle }, $event.target.value)"
/>
<div class="controls">
<button id="cancel-button" class="control-button" @click="onCancel">Cancel</button>
<button id="save-button" class="control-button" @click="onSave">Save</button>
</div>
</div>
</main>
</template>
<script>
import CustomInput from '@/components/CustomInput.vue';
import { mapState, mapActions } from 'vuex';
export default {
name: 'Home',
components: {
CustomInput,
},
data() {
return {
editMode: false,
};
},
computed: {
...mapState(['title', 'subtitle']),
},
methods: {
...mapActions(['randomizeTitleAndSubtitle', 'updateTitleAndSubtitle']),
onEdit() {
this.editMode = true;
},
onCancel() {
this.editMode = false;
},
onSave(v) {
this.editMode = false;
console.log('returned value object: ', v);
this.$store.dispatch('UPDATE_TITLE', v.title);
this.$store.dispatch('UPDATE_SUBTITE', v.subtitle);
},
},
mounted() {
this.randomizeTitleAndSubtitle();
},
};
</script>
Upvotes: 0
Views: 4447
Reputation: 39
Figured it out and cleaned up the code related to what I was initially looking for. Added an empty object in the data as a placeholder for the emitted object values coming from the custom input component and removed unnecessary code from the custom input component as well. Cleaned it up to only the minimal which didn't include two seperate $emit events ~ only one!
Custom Input:
<template>
<div>
<label for="title">Edit Title: </label>
<input
type="text"
id="title"
:setTitle="setTitle"
ref="title"
:value="value.title"
@input="updateValue()"
/>
<label for="title">Edit Subtitle: </label>
<input
type="text"
id="subtitle"
:setSubtitle="setSubtitle"
ref="subtitle"
:value="value.subtitle"
@input="updateValue()"
/>
</div>
</template>
<script>
export default {
name: 'CustomInput',
props: {
value: {
type: Object,
required: true,
},
},
computed: {
setTitle() {
// console.log('set title: ', this.value.title);
return this.value.title;
},
setSubtitle() {
// console.log('set subtitle: ', this.value.subtitle);
return this.value.subtitle;
},
},
methods: {
updateValue() {
this.$emit('input', {
title: this.$refs.title.value,
subtitle: this.$refs.subtitle.value,
});
},
},
};
</script>
Parent:
<template>
<main class="home-page page">
<!-- <span class="bold">Title:</span> {{ title }} <br>
<span class="bold">Subtitle:</span> {{ subtitle }}
<hr> -->
<div v-if="!editMode" class="display-information">
<div class="title">
<span class="bold">Title: </span>{{title}}
</div>
<div class="subtitle">
<span class="bold">Subtitle: </span>{{subtitle}}
</div>
<div class="controls">
<button id="randomize-button" class="control-button" @click="randomizeTitleAndSubtitle">
Randomize
</button>
<button id="edit-button" class="control-button" @click="onEdit">Edit</button>
</div>
</div>
<div v-else class="edit-controls">
<CustomInput
:value="{ title, subtitle }"
@input="v => onEdit(v)"
/>
<div class="controls">
<button id="cancel-button" class="control-button" @click="onCancel">Cancel</button>
<button id="save-button" class="control-button" @click="onSave(v)">Save</button>
</div>
</div>
</main>
</template>
<script>
// @ is an alias to /src
import CustomInput from '@/components/CustomInput.vue';
import { mapState, mapActions } from 'vuex';
export default {
name: 'Home',
components: {
CustomInput,
},
data() {
return {
editMode: false,
v: {},
};
},
computed: {
...mapState(['title', 'subtitle']),
},
methods: {
...mapActions(['randomizeTitleAndSubtitle', 'updateTitleAndSubtitle']),
onEdit(v) {
this.editMode = true;
this.v = v;
console.log('returned value object: ', v);
},
onCancel() {
this.editMode = false;
},
onSave() {
this.editMode = false;
this.$store.dispatch('updateTitleAndSubtitle', this.v);
},
},
mounted() {
this.randomizeTitleAndSubtitle();
},
};
</script>
<style lang="stylus" scoped>
.bold
font-weight bold
.controls
width 100%
display flex
justify-content space-around
max-width 20rem
margin-top 2rem
margin-left auto
margin-right auto
.control-button
height 2.5rem
border-radius 1.25rem
background-color white
border 0.125rem solid black
padding-left 1.25rem
padding-right 1.25rem
&:hover
cursor pointer
background-color rgba(0, 0, 0, 0.1)
</style>
Upvotes: 0
Reputation: 138216
The error points to the @input
:
<CustomInput
:value="{ title, subtitle }" ❌
@input="onSave(v = { title, subtitle }, $event.target.value)"
/>
CustomInput
emits the input
event with a string:
export default {
⋮
computed: {
⋮
inputTitle: {
⋮
set(title) { 👇
this.$emit('input', title);
},
},
inputSubtitle: {
⋮
set(subtitle) { 👇
this.$emit('input', subtitle);
},
},
},
};
So $event
is that string, which would not contain a target
property, leading to the error you observed.
I think you might've copied that code from an @input
on an <input>
element, in which case $event
would've been an InputEvent
object.
To resolve the issue, remove the .target.value
from the markup:
<CustomInput
:value="{ title, subtitle }" ✅
@input="onSave(v = { title, subtitle }, $event)"
/>
Upvotes: 2