Vue.js single file component for loop error - vue.js

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>

Related

Dynamic Placeholder in Vue 3 with Global Component

I am trying to set dynamic text for the placeholder attribute on my search bar. Depending on the page, I want the text in the search bar to be different (I will define it in data()).
However, since the search bar component is a global component, it doesn't seem to be editable.
(As you see below is my try, I did it with v-model based on Vue docs, however when I try with placeholder it doesn't work...)
Snippet 1 - Search bar component
<template>
<!-- Search Componenet -->
<div class="mx-5 mb-3 form-group">
<br>
<input class="mb-5 form-control" type="search" :placeholder="placeholderValue" :value="modelValue" #load="$emit('update:placeholderValue', $event.target.value)" #input="$emit('update:modelValue', $event.target.value)" />
</div>
</template>
<script>
export default {
props: ['modelValue', 'placeholderValue'],
emits: ['update:modelValue', 'update:placeholderValue']
}
</script>
Snippet 2 - Album.vue
<template>
<div class="AlbumView">
<h1>{{header}}</h1>
<h2>{{header2}}</h2>
<br>
<!-- Search Componenet -->
<SearchComponent :placeholder="placeholderValue" v-model="searchQuery" />
<!-- Dynamic Song Route Button -->
<div class="button-container-all mx-5 pb-5">
<div v-for="item in datanew" :key="item.id">
{{ item.album }}
</div>
</div>
</div>
</template>
<script>
import { datatwo } from '#/data2'
export default {
data() {
return {
placeholderValue: "Search for Albums here...",
datanew: datatwo,
searchQuery: null,
header: "Browse by Album",
header2: "Select an Album:",
publicPath: process.env.BASE_URL
};
},
}
</script>
If this is possible?
If you want to do it with v-model (the Childcomponent changes the value of the placeholder) you have to use v-model:placeholder for it to work.
And also placeholderValue is not the way to go the "Value" at the end of a prop is only needed for modelValue which is the default v-model-binding (v-model="") but if you want named v-model-binding (v-model:placeholder="") you do not want to add the "Value" in the props and emits arrays.
Example:
usage of SearchComponent
<SearchComponent :placeholder="'placeholderValue'" v-model="searchQuery" />
instead of 'placeholderValue' you can put any string you want or variable. I just put the string 'placeholderValue' as an example.
SearchComponent
<template>
<!-- Search Componenet -->
<div class="mx-5 mb-3 form-group">
<br>
<input class="mb-5 form-control" type="search" :placeholder="placeholder" :value="modelValue" #load="$emit('update:placeholderValue', $event.target.value)" #input="$emit('update:modelValue', $event.target.value)" />
</div>
</template>
<script>
export default {
name: "SearchComponent",
props: ['modelValue', 'placeholder'],
emits: ['update:modelValue'],
}
</script>
<style scoped>
</style>

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 warn]: Missing required prop: "productInfo"

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.