[Vue warn]: Missing required prop: "productInfo" - vue.js

I'm fairly new to Vue so this may be obvious, but I must be missing something. I keep getting the error: [Vue warn]: Missing required prop: "productInfo" in my .vue file. It says its found in ProductSlider.vue.
ProductSlider.vue
<template>
<div
id="recommendedProducts">
<h2>{{ params.headerText }}</h2>
<div
class="wrapper">
<div class="carousel-view">
<carousel
:navigation-enabled="true"
:min-swipe-distance="1"
:per-page="5"
navigation-next-label="<i class='fas fa-3x fa-angle-right'></i>"
navigation-prev-label="<i class='fas fa-3x fa-angle-left'></i>">
<slide
v-for="product in products"
:key="product.id">
<Product
:id="product.id"
:product-info="product"/>
</slide>
</carousel>
</div>
</div>
</div>
</template>
<script>
import Slider from './Slider'
import Product from './Product'
import {mapState, mapGetters} from 'vuex'
import axios from 'axios';
export default {
name: "Slider",
components: {
Product
},
props: {
productInfo: {
type: Object,
required: true
}
},
I chopped off the end of the ProductSlider code because the rest is very long and irrelevant.
And then here is my Product.vue:
<template>
<Product>
<div class="img-container text-center">
<a
:href="productInfo.href"
class="handCursor"
tabindex="0">
<img
v-if="productInfo.imgOverlay"
:src="productInfo.imgOverlayPath"
:alt="productInfo.title"
:aria-label="productInfo.title"
border="0"
tabindex="-1">
<img
:src="productInfo.imgURL"
:alt="productInfo.title">
</a>
</div>
<h4 class="itemTitle">{{ productInfo.title }}</h4>
<div class="price-div">
<div class="allPriceDescriptionPage">{{ productInfo.price }}</div>
</div>
<a
href="#"
tabindex="0"
name="instantadd">
<div class="btn_CA_Search buttonSearch gradient"> Add to Cart</div>
</a>
</Product>
</template>
<script>
export default {
name: "Product",
props: {
productInfo: {
type: Object,
required: true,
},
},
}
</script>
I have productInfo in the props so I'm not sure why I keep getting this error.

I think you confused props and the child component's props, when you have:
<Product
:id="product.id"
:product-info="product"/>
You have a prop productInfo defined in the child component Product, not in the component ProductSlider itself, and if you define prop productInfo in ProductSlider, then you have to pass it down from the parent component, that is you need to have <ProductSlider :product-info="..."/> whenever ProductSlider is used.
Notice <Product :product-info="product"/> doesn't mean you are using the value of productInfo, product-info is the name of prop that's defined in Product component.
I think a fair analogy to use is if you think of your component as a function, props are function parameters, and required function parameters need to provided when they are called.
For your case, to eliminate the error, you can simply remove the productInfo prop from ProductSlider component since it's not used in that component.

Related

Sending object as a props in Vue.js and getting TypeError and Warning

I am trying to pass an object into my child component but it comes undefined. So my parent component:
<template>
<section id="cart-page" class="row">
<div v-for="items in cart" :key="items.product.id">
<div class="col-lg-8 col-md-12 col-sm-12 col-xs-12">
<div class="title">WARENKORB</div>
<SoldTickets :items = "items"/>
</div>
</div>
</section>
</template>
<script>
import image from "../../../../img/Hallenbad.jpg";
import CheckBox from "../components/CheckBox";
import SoldTickets from "../components/SoldTickets";
export default {
components: {
CheckBox,
SoldTickets,
},
data(){
return {
image: image,
};
},
computed: {
cart() {
return this.$store.state.cart;
},
},
};
</script>
So I store cart into Vuex store and I am taking it right and it is an object like in the below:
So I want to send this cart.attributes.items object to SoldTickets component to display them inside of the component.
<template>
<div id="sold-tickets">
<div class="card">
<div class="sold-tickets-actions">
<div class="sold-tickets-inner">
<img class="sold-tickets-image" :src="image" alt="Sold Ticket"/>
</div>
</div>
<div class="sold-tickets-actions properties">
<div class="sold-tickets-inner">
<div class="ticket-details">
<div class="ticket-prop">
<div class="ticket-name">{{ items.product_name }}</div>
<div class="ticket-type">{{ items.variation_name }}</div>
</div>
</div>
<DeleteButton />
</div>
</div>
</div>
</div>
</template>
<script>
import image from "../../../../img/Hallenbad.jpg";
import DeleteButton from "./DeleteButton";
import cartHelper from "../helpers/cartHelper";
export default {
props: {
items: Object,
},
components: {DeleteButton},
data() {
return {
image: image,
};
},
};
</script>
But I am getting errors. vue.esm.js:628 [Vue warn]: Error in render: "TypeError: Cannot read properties of undefined (reading 'id')" and TypeError: Cannot read properties of undefined (reading 'id'). And if I delete ':key="items.product.id"' from parent component this time I am getting a warning but items cannot be displayed again. [Vue warn]: Invalid prop: type check failed for prop "items". Expected Object, got String with value "00e84ffb-1fbf-00bf-d3cc-adbcc795e060" and [Vue warn]: Invalid prop: type check failed for prop "items". Expected Object, got String with value "carts"
But the thing is if I try to display the items in the parent component, it works without warning & erros. So why do you think it is happening? Thanks for the helps.
The problem here is that items are an array of objects, as can be seen from the console.log(), but you treat it as an object in your code. You can change that by either passing a certain object to the child component like this:
<div v-for="items in cart" :key="items.product.id">
<div class="col-lg-8 col-md-12 col-sm-12 col-xs-12">
<div class="title">WARENKORB</div>
<SoldTickets :items = "items[0]"/> --> only pass first object
</div>
</div>
Or you change your child component to accept arrays and then loop through the array in the template like this:
<template>
<div id="sold-tickets">
<div class="card">
<div class="sold-tickets-actions">
<div class="sold-tickets-inner">
<img class="sold-tickets-image" :src="image" alt="Sold Ticket"/>
</div>
</div>
<div v-for="item in items" :key="item.product.id" class="sold-tickets-actions properties">
<div class="sold-tickets-inner">
<div class="ticket-details">
<div class="ticket-prop">
<div class="ticket-name">{{ item.product_name }}</div>
<div class="ticket-type">{{ item.variation_name }}</div>
</div>
</div>
<DeleteButton />
</div>
</div>
</div>
</div>
</template>
<script>
import image from "../../../../img/Hallenbad.jpg";
import DeleteButton from "./DeleteButton";
import cartHelper from "../helpers/cartHelper";
export default {
props: {
items: Array,
},
components: {DeleteButton},
data() {
return {
image: image,
};
},
};
</script>
The problem is that you are looping through the object cart which has many keys, and some of their values are strings (eg: "00e84ffb-1fbf-00bf-d3cc-adbcc795e060"), at the same time your child component expects to receive an object. That's why it's spitting out the errors. You can read more about List Rendering with an Object here.
My suggestion:
If you need cart.attributes.items in your child component, then just send it, change your computed property cart to an array:
computed: {
cart() {
return this.$store.state.cart.attributes?.items || [];
}
}
in the template, passing item array into the child component, the variable item should be in singular form, you can name whatever you want, but for the sake of clarity, please use singular form:
<div v-for="item in cart" :key="item.id">
<div class="col-lg-8 col-md-12 col-sm-12 col-xs-12">
<div class="title">WARENKORB</div>
<SoldTickets :item = "item"/>
</div>
</div>
don't forget to change your props declaration:
props: {
items: Array,
}

Executing js on slot

I'm a beginner in web development and I'm trying to help out friends restarting an old game. I'm in charge of the tooltip component but I hit a wall...
There are many Vue components and in a lot of them I want to call a child component named Tooltip, I'm using vue-tippy for easy configuration. This is the component:
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<slot name='tooltip-content'>
</slot>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods:{
formatContent(value) {
if (! value) return '';
return formatText(value.toString());
}
},
}
</script>
In one of the other components I try to use the tooltip:
<template>
<a class="action-button" href="#">
<Tooltip>
<template #tooltip-trigger>
<span v-if="action.movementPointCost > 0">{{ action.movementPointCost }}<img src="#/assets/images/pm.png" alt="mp"></span>
<span v-else-if="action.actionPointCost > 0">{{ action.actionPointCost }}<img src="#/assets/images/pa.png" alt="ap"></span>
<span v-if="action.canExecute">{{ action.name }}</span>
<span v-else><s>{{ action.name }}</s></span>
<span v-if="action.successRate < 100" class="success-rate"> ({{ action.successRate }}%)</span>
</template>
<template #tooltip-content>
<h1>{{action.name}}</h1>
<p>{{action.description}}</p>
</template>
</Tooltip>
</a>
</template>
<script>
import Tooltip from "#/components/Utils/ToolTip";
export default {
props: {
action: Object
},
components: {Tooltip}
};
</script>
From here everything is fine, the tooltip is correctly displayed with the proper content.
The thing is, the text in the {{ named.description }} needs to be formatted with the formatContent content. I know I can use the props, the components would look like that:
Tooltip.vue:
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<h1 v-html="formatContent(title)" />
<p v-html="formatContent(content)"/>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods:{
formatContent(value) {
if (! value) return '';
return formatText(value.toString());
}
},
props: {
title: {
type: String,
required: true
},
content: {
type: Array,
required: true
}
}
}
</script>
Parent.vue:
<template>
<a class="action-button" href="#">
<Tooltip :title="action.name" :content="action.description">
<template v-slot:tooltip-trigger>
<span v-if="action.movementPointCost > 0">{{ action.movementPointCost }}<img src="#/assets/images/pm.png" alt="mp"></span>
<span v-else-if="action.actionPointCost > 0">{{ action.actionPointCost }}<img src="#/assets/images/pa.png" alt="ap"></span>
<span v-if="action.canExecute">{{ action.name }}</span>
<span v-else><s>{{ action.name }}</s></span>
<span v-if="action.successRate < 100" class="success-rate"> ({{ action.successRate }}%)</span>
</template>
</Tooltip>
</a>
</template>
<script>
import Tooltip from "#/components/Utils/ToolTip";
export default {
props: {
action: Object
},
components: {Tooltip}
};
</script>
But I need to use a slot in the tooltip component because we'll have some "extensive" lists with v-for.
Is there a way to pass the data from a slot into a JS function?
If I understand you correctly, you're looking for scoped slots here.
These will allow you to pass information (including methods) from child components (the components with <slot> elements) back to the parents (the component(s) filling those slots), allowing parents to use chosen information directly in the slotted-in content.
In this case, we can give parents access to formatContent(), which will allow them to pass in content that uses it directly. This allows us to keep the flexibility of slots, with the data passing of props.
To add this to your example, we add some "scope" to your content slot in Tooltip.vue. This just means we one or more attributes to your <slot> element, in this case, formatContent:
<!-- Tooltip.vue -->
<template>
<tippy class="tippy-tooltip">
<slot name='tooltip-trigger'></slot>
<template #content>
<!-- Attributes we add or bind to this slot (eg. formatContent) -->
<!-- become available to components using the slot -->
<slot name='tooltip-content' :formatContent="formatContent"></slot>
</template>
</tippy>
</template>
<script>
import { formatText } from "#/utils/formatText";
export default {
name: "Tooltip",
methods: {
formatContent(value) {
// Rewrote as a ternary, but keep what you're comfortable with
return !value ? '' : formatText(value.toString());
}
},
}
</script>
Now that we've added some scope to the slot, parents filling the slot with content can use it by invoking a slot's "scope":
<!-- Parent.vue -->
<template>
<a class="action-button" href="#">
<Tooltip>
. . .
<template #tooltip-content="{ formatContent }">
<!-- Elements in this slot now have access to 'formatContent' -->
<h1>{{ formatContent(action.name) }}</h1>
<p>{{ formatContent(action.description) }}</p>
</template>
</Tooltip>
</a>
</template>
. . .
Sidenote: I prefer to use the destructured syntax for slot scope, because I feel it's clearer, and you only have to expose what you're actually using:
<template #tooltip-content="{ formatContent }">
But you can also use a variable name here if your prefer, which will become an object which has all your slot content as properties. Eg.:
<template #tooltip-content="slotProps">
<!-- 'formatContent' is now a property of 'slotProps' -->
<h1>{{ slotProps.formatContent(action.name) }}</h1>
<p>{{ slotProps.formatContent(action.description) }}</p>
</template>
If you still need the v-html rendering, you can still do that in the slot:
<template #tooltip-content="{ formatContent }">
<h1 v-html="formatContent(title)" />
<p v-html="formatContent(content)"/>
</template>

Implementing a reusable tabbed component using Vuejs

I'm trying to implement a tabbed reusable component in vueJs but I'm getting an error that a particular component is not defined. Below are both components
//TabComponent
<template>
<div>
<div class="row">
<div class="col-lg-12 col-xl-12">
<div class="card-box">
<ul class="nav nav-tabs nav-bordered">
<li v-for="tab in tabs" :key="tab" class="nav-item">
{{tab}}
</li>
</ul>
<div class="tab-content">
<component :is="selectedComponent"></component>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'TabComponent',
props: [ selectedComponent, tabs ] //The error is coming from this line
}
</script>
I have imported it to this component and currently it shows the error
Uncaught ReferenceError: selectedComponent is not defined
//Entitlements component
<template>
<div>
<tab-component :tabs="tabs" :selectedComponent="selectedComponent" />
</div>
</template>
<script>
import TabComponent from "../../../components/TabComponent";
import List from "./Entitlements/List";
import MyEntitlements from "./Entitlements/MyEntitlements";
export default {
name: 'Entitlements',
components: {List, MyEntitlements, TabComponent},
data(){
return{
tabs: ['List', 'MyEntitlements'],
selectedComponent: 'List',
}
}
}
</script>
HTML attribute names are case-insensitive, so browsers will interpret
any uppercase characters as lowercase. That means when you’re using
in-DOM templates, camelCased prop names need to use their kebab-cased
(hyphen-delimited) equivalents (source)
Try with:
<tab-component :tabs="tabs" :selected-component="selectedComponent" />
Edit:
If you define props as an array, change the list with strings (see "Prop types" here):
props: [ 'selectedComponent', 'tabs' ]

Vue how to customize global navbar at view level

Im super new to Vue.
i have a Vue-CLI app, which have a navbar and content.
Navbar is common to all pages, but i want to customize in each page whit some additional content.
Example:
Common-> home | about
View home -> home | about | your are in view home
View about -> home | about | your are in view about
router/index.js
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from '../views/Home.vue';
import NavBar from '#/components/NavBar.vue';
Vue.use(VueRouter);
Vue.component('nav-bar', NavBar);
//...
components/navbar.vue
<template>
<div>
<b-nav-item to="/">home</b-nav-item>
<b-nav-item to="/about">about</b-nav-item>
{{customContent}}
</div>
</template>
<script>
export default {
name: 'NavBar',
props: {
customContent: {
type: String,
default: 'default Content',
},
},
};
</script>
App.vue
<template>
<div id="app">
<nav-bar />
<div class="container-fluid">
<router-view />
</div>
</div>
</template>
views/home.vue
<template>
<div class="row">
<div class="col-12">
<image-card :images="images"/>
</div>
</div>
</template>
<script>
//how can i customize here the navbar by adding for example 'your are in view home'???
</script>
Thanks so much!
There are a few ways in which you can solve this problem. I'll list two of them.
1. Update NavBar by $route
In this approach, the NavBar component already contains all of the possible combinations, and will display the relevant portion(s) depending on what $route contains.
Here's some pseudo code:
navbar.vue
<template>
<div class="navbar">
<div class="navbar-left>
APPNAME
</div>
<div v-if="name === 'landing'">
...
</div>
<div v-else-if="name === 'room'">
...
</div>
</div>
</template>
App.vue
<template>
<div id="app">
<NavBar :name="$route.name"/>
<main>
<router-view/>
</main>
</div>
</template>
In this example, the NavBar component is very rigid, and doesn't really lend itself to much reuse. However, it does encapsulate all the relevant code relating to the nav bar.
2. Extensible NavBar with slots
In this approach, the NavBar only provides the bare-minimum to create a nav bar. The rest of the route-specific elements are to be filled in by the views.
navbar.vue
<template>
<div class="navbar">
<div class="navbar-left">
<div class="navbar-brand">
APPNAME
</div>
<slot name="left"></slot>
</div>
<div class="navbar-right">
<slot name="right"></slot>
</div>
</div>
</template>
App.vue
<template>
<div id="app">
<router-view/>
</div>
</template>
landing.vue
<template>
<div>
<header>
<NavBar>
<template slot="right">
<span>
<div class="navbar-item">
<div class="buttons">
<button class="button" #click="...">Start Watching</button>
</div>
</div>
</span>
</template>
</NavBar>
</header>
<main>
...
</main>
</div>
</template>
This approach has a bit of repetition in terms of DOM elements, but gives you an extremely flexible NavBar that can be customized by each view.
The approach you want to use depends on what is important to you.
If strict encapsulation is what you want, then you may want to use approach 1, as all of the NavBar-related code is contained within a single file.
However, if you believe that there is a potential for reuse, or if you would like all view-related code to live in one place, then it makes sense to use slots instead and extend the NavBar as required by each view.
I use a breadcrumb to achieve a similar thing. Just an idea but Vue router allows you to add meta data to the current route which you always have access to
router.js
path: '/add',
name: 'add',
component: () => import(/* webpackChunkName: "add" */ '../../views/Add.vue'),
meta: {
breadCrumb: [
{ name: 'Add New' }
]
},
Notice the meta object attached to the route.. this will be used to describe the current view.
Breadcrumb.vue component
<template>
<div class="breadcrumb">
<ul class="d-flex m-0 p-0"
<li
v-for="(breadcrumb, idx) in breadcrumbList"
:key="idx">
{{ breadcrumb.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Breadcrumb',
data () {
return {
breadcrumbList: []
}
},
mounted () { this.updateList() },
watch: { '$route' () { this.updateList() } },
methods: {
routeTo (pRouteTo) {
if (this.breadcrumbList[pRouteTo].link) this.$router.push(this.breadcrumbList[pRouteTo].link)
},
updateList () { this.breadcrumbList = this.$route.meta.breadCrumb },
formatPath(path) {
const newPath = path.replace(/\//g, " > ")
return newPath
}
}
}
</script>
And then you can import the breadcrumb into your navbar or where ever you would like to place it
<Breadcrumb class="breadcrumb" />
import Breadcrumb from '#/components/Breadcrumb.vue'
components: {Breadcrumb}
So basically the breadcrumb will always watch your current route and change the data based on the meta data you provide in your router.js file
You can access to router name like this:
<div v-if="this.$route.name == 'home'">
<HeaderTransparent />
</div>
<div v-else>
<HeaderWhite />
</div>

Vue.js single file component for loop error

For some reason i cannot get a loop working within a single file .vue file. The following error occurs:
Property or method "value" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
(Getting the same error for the key value)
.vue File:
<template>
<div class="grid-view container mx-auto flex items-center py-4">
<div v-bind:for="(value, key) in testdata">
{{ key }}: {{ value }}
</div>
</div>
</template>
<script>
export default {
props: [
'testdata'
]
}
</script>
HTML:
<test :testdata="{'name':'sku','name':'test'}"></test>
Hope someone can help!
Replace v-bind:for= with v-for=
<template>
<div class="grid-view container mx-auto flex items-center py-4">
<div v-for="(value, index) in testData" :key="index">
{{ value }}
</div>
</div>
</template>
<script>
export default {
props: [
'testData'
]
}
</script>
Your test data is not correct as you're using one object which contains two similar name keys:
<test :test-data="{'name':'sku','name':'test'}"></test>
..so put each object into an array:
<test :test-data="[{ name: "sku" }, { name: "test" }]"></test>
A note for code conventions:
When naming the props in HTML, use kebap case:
:test-data instead of :testdata
When naming the props in JS, use camel case:
testData instead of testdata
Change v-bind:for to v-for
<template>
<div class="grid-view container mx-auto flex items-center py-4">
<div v-for="(value, key) in testdata">
{{ key }}: {{ value }}
</div>
</div>
</template>
<script>
export default {
props: [
'testdata'
]
}
</script>