Waffles
Waffles

Reputation: 39

Vue2 - "TypeError: Cannot read properties of undefined (reading 'value')"

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

Answers (2)

Waffles
Waffles

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

tony19
tony19

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.

Solution

To resolve the issue, remove the .target.value from the markup:

<CustomInput
   :value="{ title, subtitle }"              ✅
   @input="onSave(v = { title, subtitle }, $event)"
   />

demo

Upvotes: 2

Related Questions