Sole
Sole

Reputation: 3350

TypeError: Cannot read property 'push' of undefined Vue JS

I have a component which is trying to push data to another sub component, Abit confused here as I am new to Vue.js. I have tried to import the subcomponent into the main one, but the push for the add item is causing the issue? Any ideas?

I get the error:

TypeError: Cannot read property 'push' of undefined

I cant see why or where?

Main component:

<template>
  <div class="container search">
    <list></list>

    <div class="jumbotron">
      <h1 class="display-4">{{title}}</h1>
      <p class="lead">{{intro}}</p>
      <hr class="my-4">
      <p v-if="validated" :class="errorTextClass">Enter a valid search term</p>

      <button
        type="button"
        class="btn btn-primary btn-lg mb-3"
        v-on:click="refreshPage"
        v-if="result.length > 1"
      >
        <font-awesome-icon icon="redo"/>Start again
      </button>
      <input
        class="form-control form-control-lg mb-3"
        type="search"
        placeholder="Search"
        aria-label="Search"
        v-model="search"
        required
        autocomplete="off"
        id="search"
      >

      <div v-for="(result, index) in result" :key="index">
        <div class="media mb-4">
          <img
            :src="resizeArtworkUrl(result)"
            alt="Album Cover"
            class="album-cover align-self-start mr-3"
          >
          <div class="media-body">
            <h4 class="mt-0">
              <button
                type="button"
                class="btn btn-primary btn-lg mb-3 float-right"
                v-on:click="addItem(result)"
              >
                <font-awesome-icon icon="plus"/>
              </button>
              <b>{{result.collectionName}}</b>
            </h4>
            <h6 class="mt-0">{{result.artistName}}</h6>
            <p class="mt-0">{{result.primaryGenreName}}</p>
          </div>
        </div>
      </div>

      <div :class="loadingClass" v-if="loading"></div>

      <button
        class="btn btn-success btn-lg btn-block mb-3"
        type="submit"
        v-on:click="getData"
        v-if="result.length < 1"
      >
        <font-awesome-icon icon="search"/>Search
      </button>
    </div>
  </div>
</template>

<script>
import List from "../components/myList.vue";

export default {
  name: "Hero",
  props: {
    List
  },
  components: {
    List
  },
  data: function() {
    return {
      title: "Simple Search",
      intro: "This is a simple hero unit, a simple jumbotron-style.",
      subintro:
        "It uses utility classes for typography and spacing to space content out.",
      result: [],
      errors: [],
      search: "",
      loading: "",
      message: false,
      isValidationAllowed: false,
      loadingClass: "loading",
      errorTextClass: "error-text"
    };
  },

  watch: {
    search: function(val) {
      if (!val) {
        this.result = [];
      }
    }
  },

  computed: {
    validated() {
      return this.isValidationAllowed && !this.search;
    }
  },

  methods: {
    getData: function() {
      this.isValidationAllowed = true;
      this.loading = true;
      fetch(`xxxxxxxx`)
        .then(response => response.json())
        .then(data => {
          this.result = data.results;
          this.loading = false;
          /* eslint-disable no-console */
          console.log(data);
          /* eslint-disable no-console */
        });
    },
    refreshPage: function() {
      this.search = "";
    },
    addItem: function(result) {
      this.myList.push(result);
    },

    resizeArtworkUrl(result) {
      return result.artworkUrl100.replace("100x100", "160x160");
    }
  }
};
</script>

<style>
.loading {
  background-image: url("../assets/Rolling-1s-42px.gif");
  background-repeat: no-repeat;
  height: 50px;
  width: 50px;
  margin: 15px;
  margin-left: auto;
  margin-right: auto;
}

.error-text {
  color: red;
}

.media {
  text-align: left;
}

.album-cover {
  width: 80px;
  height: auto;
}
</style>

Sub component:

<template>
  <div class="mb-5 container">
    <button type="button" class="btn btn-primary mt-2 mb-2 btn-block">
      My List
      <span class="badge badge-light">{{myList.length}}</span>
    </button>
    <ul class="list-group" v-for="(result, index) in myList" :key="index">
      <li class="list-group-item">
        <b>{{result.collectionName}}</b>
        <h6 class="mt-0">{{result.artistName}}</h6>
        <p class="mt-0">{{result.primaryGenreName}}</p>
      </li>
    </ul>
  </div>
</template>

<script>

export default {
  name: "List",

  data: function() {
    return {
      myList: [],
      result: []
    };
  }
}
</script>

Upvotes: 0

Views: 2517

Answers (1)

Decade Moon
Decade Moon

Reputation: 34306

Data should always flow from parent to child components; child components shouldn't be modifying the data belonging to parent components.

If you want the main component to be able to push an item to myList, then myList should belong to the main component and you pass it to the sub component via a prop.

Simplified example:

Main component

<list :list="list"/>
<button @click="addItem">Add Item</button>
import List from './list.vue'

export default {
  components: {
    List
  },
  data() {
    return {
      list: []
    }
  },
  methods: {
    addItem() {
      this.list.push('New Item')
    }
  }
}

List component

<ul>
  <li v-for="item of list">{{ item }}</li>
</ul>
export default {
  props: ['list']
}

Upvotes: 1

Related Questions