Wondering how to pass data from the parent component (Home route) to the child component (playlist route) using props? Encountering a
"TypeError: Cannot read property 'length' of undefined"
error in the child component? These two components are currently on the same page, but moving the child component to its own route is triggering this issue.
App.vue
<template>
<div id="app">
<Header></Header>
<router-view></router-view>
<Footer></Footer>
</div>
</template>
<script>
import Header from './components/header.vue'
import Footer from './components/footer.vue'
export default {
name: 'app',
components: {
Header,
Footer,
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 0px;
}
</style>
Main.js
import Vue from 'vue';
import App from './App.vue';
import "jquery";
import "bootstrap";
import VueRouter from 'vue-router';
import "bootstrap/dist/css/bootstrap.min.css"
import { library } from '@fortawesome/fontawesome-svg-core'
import { faSearch } from '@fortawesome/free-solid-svg-icons'
import { faRedo } from '@fortawesome/free-solid-svg-icons'
import { faPlus } from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
import Home from './components/home.vue'
import Next from './components/next.vue'
import List from './components/myList.vue'
library.add(faSearch)
library.add(faRedo)
library.add(faPlus)
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.use(VueRouter);
Vue.config.productionTip = false
const router = new VueRouter({
routes: [
{
path: '/home',
component: Home
},
{
path: '/next',
component: Next
},
{
path: '/playlist',
component: List
}
]
});
new Vue({
render: h => h(App),
router
}).$mount('#app')
Parent component:
<template>
<div class="container search">
<div class="jumbotron" style="clear:both">
<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>-->
<button
type="button"
class="btn btn-primary btn-lg mb-3 float-right"
v-on:click="addItem(result)"
:disabled="result.disableButton"
>
<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",
components: {
List
},
data: function() {
return {
title: "Simple Search",
isActive: true,
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: [],
List: [],
search: "",
loading: "",
message: false,
isValidationAllowed: false,
loadingClass: "loading",
errorTextClass: "error-text",
disableButton: false
};
},
watch: {
search: function(val) {
if (!val) {
this.result = [];
}
}
},
computed: {
validated() {
return this.isValidationAllowed && !this.search;
},
isDisabled: function() {
return !this.terms;
}
},
methods: {
getData: function() {
this.isValidationAllowed = true;
this.loading = true;
fetch(`https://thisapi.com/api`)
.then(response => response.json())
.then(data => {
this.result = data.results;
this.loading = false;
/* eslint-disable no-console */
console.log(data);
/* eslint-disable no-console */
});
},
toggleClass: function() {
// Check value
if (this.isActive) {
this.isActive = false;
} else {
this.isActive = true;
}
},
refreshPage: function() {
this.search = "";
},
addItem: function(result) {
result.disableButton = true; // Or result['disableButton'] = true;
this.List.push(result);
/* eslint-disable no-console */
console.log(result);
/* eslint-disable no-console */
},
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;
}
.red {
background: red;
}
.blue {
background: blue;
}
.div {
width: 100px;
height: 100px;
display: inline-block;
border: 1px solid black;
}
</style>
Child component or /list (route)
<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">{{List.length}}</span>
</button>
<div class="col-md-4 float-left p-2 mb-3 " v-for="(result, index) in List" :key="index">
<img
:src="resizeArtworkUrl(result)"
alt="Album Cover"
class="album-cover align-self-start mr-3 card-img-top"
>
<div class="card-body">
<h5 class="card-title">{{result.collectionName}}</h5>
<h6 class="mt-0 mb-2">{{result.artistName}}</h6>
<p class="mt-0 mb-2">{{result.primaryGenreName}}</p>
<button class="btn btn-danger" v-on:click="removeElement(result)">Remove</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'List',
props:
["List"],
methods: {
removeElement: function(index) {
this.List.splice(index, 1);
},
resizeArtworkUrl(result) {
return result.artworkUrl100.replace("100x100", "160x160");
}
}
};
</script>
<style scoped>
.album-cover {
width: 100%;
height: auto;
background-color: aqua;
}
</style>