I get errors in the console about the lack of the required props (height / width). How can I find out where to specify them (in what file and what should the class be called)?
I am aware that could be a dummy question so I also would be grateful if you would reccomend some viedo/tutorial in your opinion useful to get to know more devtools basics.
This is my first question, so I'm not sure what information I should provide. Let me know if anything is missing.
My console 1
Home.vue
<template>
<div id="home">
<LazyHydrate when-idle>
<SfHero >
<SfHeroItem
v-for="(hero, i) in heroes"
:key="i"
:title="$t(hero.title)"
:subtitle="$t(hero.subtitle)"
:background="hero.background"
:image="hero.image | addBasePathFilter"
:
:height="200"
:width="200"
/>
</SfHero>
</LazyHydrate>
<LazyHydrate when-visible>
<SfBannerGrid :banner-grid="1" >
<template v-for="item in banners" v-slot:[item.slot]>
<SfBanner
:key="item.slot"
:title="$t(item.title)"
:subtitle="$t(item.subtitle)"
:description="$t(item.description)"
:button-text="$t(item.buttonText)"
:link="localePath(item.link)"
:image="item.image | addBasePathFilter"
:
:height="200"
:width="200"
/>
</template>
</SfBannerGrid>
</LazyHydrate>
<LazyHydrate when-visible>
<div >
<SfHeading :title="$t('Match with it')" :level="2" />
<nuxt-link :to="localePath('/c/women')" >See all</nuxt-link>
</div>
</LazyHydrate>
<LazyHydrate when-visible>
<SfCarousel
:settings="{ peek: 16, breakpoints: { 1023: { peek: 0, perView: 2 } } }"
>
<template #prev="{go}">
<SfArrow
aria-label="prev"
@click="go('prev')"
/>
</template>
<template #next="{go}">
<SfArrow
aria-label="next"
@click="go('next')"
/>
</template>
<SfCarouselItem
v-for="(product, i) in products"
:key="i"
>
<SfProductCard
:title="product._name"
:image="productGetters.getCoverImage(product) | addBasePathFilter"
image-tag="nuxt-img"
:nuxt-img-config="{
format: 'webp',
fit: 'fill'
}"
:image-width="216"
:image-height="290"
:regular-price="productPriceTransform(product).regular"
:special-price="productPriceTransform(product).special"
:is-added-to-cart="isInCart({ product })"
:is-in-wishlist="isInWishlist({ product })"
show-add-to-cart-button
:link="localePath(`/p/${productGetters.getSlug(product)}/${productGetters.getSku(product)}`)"
@click:add-to-cart="addToCart({ product, quantity: 1 })"
@click:wishlist="!isInWishlist({ product }) ? addProductToWishlist(product) : removeProductFromWishlist(product)"
/>
</SfCarouselItem>
</SfCarousel>
</LazyHydrate>
<LazyHydrate when-visible>
<SfCallToAction
:title="$t('Subscribe to Newsletters')"
:button-text="$t('Subscribe')"
:description="$t('Be aware of upcoming sales and events. Receive gifts and special offers!')"
:image="'/homepage/newsletter.webp' | addBasePathFilter"
>
<template #button>
<SfButton
data-testid="cta-button"
@click="handleNewsletterClick"
>
{{ $t('Subscribe') }}
</SfButton>
</template>
</SfCallToAction>
</LazyHydrate>
<LazyHydrate when-visible>
<NewsletterModal @email-submitted="onSubscribe" />
</LazyHydrate>
</div>
</template>
<script>
import {
SfHero,
SfBanner,
SfCallToAction,
SfCarousel,
SfProductCard,
SfBannerGrid,
SfHeading,
SfArrow,
SfButton
} from '@storefront-ui/vue';
import LazyHydrate from 'vue-lazy-hydration';
import { ref, computed, watch, useContext } from '@nuxtjs/composition-api';
import { onSSR } from '@vue-storefront/core';
import {
useCart,
useFacet,
useWishlist,
useCurrency,
facetGetters,
productGetters,
wishlistGetters,
productPriceTransform
} from '@vsf-enterprise/commercetools';
import NewsletterModal from '~/components/NewsletterModal.vue';
import { useUiState, useUiNotification } from '../composables';
export default {
name: 'Home',
setup() {
const { app: { i18n } } = useContext();
const { toggleNewsletterModal } = useUiState();
const { send } = useUiNotification();
const {
isInCart,
addItem: addItemToCart,
error: cartError
} = useCart();
const { result, search } = useFacet('home');
const { currency } = useCurrency();
const { addItem: addItemToWishlist, isInWishlist, removeItem: removeItemFromWishlist, wishlist, error: wishlistError } = useWishlist();
const products = computed(() => facetGetters.getProducts(result.value));
const fetchProducts = async () => {
await search({
filters: {},
page: 1,
itemsPerPage: 12,
sort: 'latest',
phrase: ''
});
};
watch(currency, async () => {
await fetchProducts();
});
onSSR(async () => {
await fetchProducts();
});
const mocks = {
heroes: [
{
title: 'Colorful summer dresses are already in store',
subtitle: 'SUMMER COLLECTION 2022',
background: '#eceff1',
image: '/homepage/bannerH.webp'
},
{
title: 'Colorful summer dresses are already in store',
subtitle: 'SUMMER COLLECTION 2022',
background: '#efebe9',
image: '/homepage/bannerA.webp',
className:
'sf-hero-item--position-bg-top-left sf-hero-item--align-right'
},
{
title: 'Colorful summer dresses are already in store',
subtitle: 'SUMMER COLLECTION 2022',
background: '#fce4ec',
image: '/homepage/bannerB.webp'
}
],
banners: [
{
slot: 'banner-A',
subtitle: 'Dresses',
title: 'Cocktail & Party',
description: 'Find stunning women\'s cocktail dresses and party dresses. Stand out in lace and metallic cocktail dresses from all your favorite brands.',
buttonText: 'Shop now',
image: '/homepage/bannerF.webp',
class: 'sf-banner--slim desktop-only',
link: '/c/women/women-clothing-skirts'
},
{
slot: 'banner-B',
subtitle: 'Dresses',
title: 'Linen Dresses',
description: 'Find stunning women\'s cocktail dresses and party dresses. Stand out in lace and metallic cocktail dresses from all your favorite brands.',
buttonText: 'Shop now',
image: '/homepage/bannerE.webp',
class: 'sf-banner--slim banner-central desktop-only',
link: '/c/women/women-clothing-dresses'
},
{
slot: 'banner-C',
subtitle: 'T-Shirts',
title: 'The Office Life',
image: '/homepage/bannerC.webp',
class: 'sf-banner--slim banner__tshirt',
link: '/c/women/women-clothing-shirts'
},
{
slot: 'banner-D',
subtitle: 'Summer Sandals',
title: 'Eco Sandals',
image: '/homepage/bannerG.webp',
class: 'sf-banner--slim',
link: '/c/women/women-shoes-sandals'
}
]
};
const heroes = ref(mocks.heroes);
const banners = ref(mocks.banners);
const handleNewsletterClick = () => {
toggleNewsletterModal();
};
const onSubscribe = (emailAddress) => {
console.log(`Email ${emailAddress} was added to newsletter.`);
toggleNewsletterModal();
};
const addToCart = async ({ product, quantity }) => {
const { id, sku } = product;
await addItemToCart({
product: { id, sku },
quantity
});
if (!cartError.value.addItem) {
send({
type: 'success',
message: i18n.t('Product has been added to the cart.')
});
}
};
const addProductToWishlist = async (product) => {
await addItemToWishlist({ product });
if (!wishlistError.value.addItem) {
send({
type: 'success',
message: i18n.t('Product has been added to the wishlist.')
});
}
};
const removeProductFromWishlist = async (productItem) => {
const productsInWhishlist = computed(() => wishlistGetters.getItems(wishlist.value));
const product = productsInWhishlist.value.find(wishlistProduct => wishlistProduct.variant.sku === productItem.sku);
await removeItemFromWishlist({ product });
if (!wishlistError.value.removeItem) {
send({
type: 'success',
message: i18n.t('Product has been removed from the wishlist.')
});
}
};
return {
heroes,
banners,
products,
productGetters,
handleNewsletterClick,
onSubscribe,
isInCart,
addToCart,
addProductToWishlist,
isInWishlist,
removeProductFromWishlist,
productPriceTransform
};
},
components: {
LazyHydrate,
NewsletterModal,
SfArrow,
SfBanner,
SfBannerGrid,
SfButton,
SfCallToAction,
SfCarousel,
SfHeading,
SfHero,
SfProductCard
},
beforeRouteEnter (_, _2, next) { next('/home-page') }
};
</script>
<style lang="scss">
.carousel__item__product {
.sf-product-card__title {
margin: var(--spacer-base) 0 var(--spacer-xs) 0;
}
.sf-product-card__add-button {
margin-bottom: var(--spacer-xl);
}
}
</style>
<style lang="scss" scoped>
#home {
box-sizing: border-box;
padding: 0 var(--spacer-sm);
@include for-desktop {
max-width: 1240px;
padding: 0;
margin: 0 auto;
}
}
.hero {
margin: var(--spacer-xl) auto var(--spacer-lg);
--hero-item-background-position: center;
@include for-desktop {
margin: var(--spacer-xl) auto var(--spacer-2xl);
}
.sf-hero-item {
min-height: 230px;
&:nth-child(even) {
--hero-item-background-position: left;
@include for-mobile {
--hero-item-background-position: 30%;
::v-deep .sf-hero-item__subtitle,
::v-deep .sf-hero-item__title {
text-align: right;
width: 100%;
padding-left: var(--spacer-sm);
}
}
}
}
::v-deep .sf-hero__control {
&--right,
&--left {
display: none;
}
}
}
.banner-grid {
--banner-container-width: 50%;
margin: var(--spacer-xl) 0;
::v-deep .sf-link:hover {
color: var(--c-white);
}
@include for-desktop {
margin: var(--spacer-2xl) 0;
::v-deep .sf-link {
--button-width: auto;
text-decoration: none;
}
}
}
.banner {
&__tshirt {
background-position: left;
}
&-central {
@include for-desktop {
--banner-container-flex: 0 0 70%;
}
}
}
.similar-products {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: var(--spacer-2xs);
--heading-padding: 0;
border-bottom: 1px var(--c-light) solid;
@include for-desktop {
border-bottom: 0;
justify-content: center;
padding-bottom: 0;
}
}
.call-to-action {
background-position: right;
margin: var(--spacer-xs) 0;
@include for-desktop {
margin: var(--spacer-xl) 0 var(--spacer-2xl) 0;
}
}
.carousel {
margin: 0 calc(0 - var(--spacer-sm)) 0 0;
@include for-desktop {
margin: 0;
}
&__item {
margin: 1.375rem 0 2.5rem 0;
@include for-desktop {
margin: var(--spacer-xl) 0 var(--spacer-xl) 0;
}
&__product {
--product-card-add-button-transform: translate3d(0, 30%, 0);
::v-deep .sf-product-card {
&__title {
margin: var(--spacer-base) 0 var(--spacer-xs) 0;
}
&__add-button {
margin-bottom: var(--spacer-xl);
}
}
}
}
::v-deep .sf-arrow--long .sf-arrow--right {
--arrow-icon-transform: rotate(180deg);
-webkit-transform-origin: center;
transform-origin: center;
}
}
</style>
default.vue
<template>
<div>
<RenderContent v-if="styleGuide.length" :content="styleGuide" />
<LazyHydrate when-visible>
<TopBar />
</LazyHydrate>
<LazyHydrate when-idle>
<AppHeader />
</LazyHydrate>
<div id="layout">
<nuxt :key="$route.fullPath" />
<LazyHydrate when-visible>
<BottomNavigation />
</LazyHydrate>
<CartSidebar />
<WishlistSidebar />
<FiltersSidebar />
<LoginModal />
<Notification />
</div>
<LazyHydrate when-visible>
<AppFooter />
</LazyHydrate>
</div>
</template>
<script>
import AppHeader from '~/components/AppHeader.vue'
import BottomNavigation from '~/components/BottomNavigation.vue'
import AppFooter from '~/components/AppFooter.vue'
import TopBar from '~/components/TopBar.vue'
import CartSidebar from '~/components/CartSidebar.vue'
import WishlistSidebar from '~/components/WishlistSidebar.vue'
import FiltersSidebar from '~/components/FiltersSidebar.vue'
import LoginModal from '~/components/LoginModal.vue'
import Notification from '~/components/Notification'
import useCmsLayout from '~/composables/useCmsLayout'
import { onMounted } from '@vue/composition-api'
import LazyHydrate from 'vue-lazy-hydration'
import { useStore, useUser, useWishlist } from '@vsf-enterprise/commercetools'
import { onSSR } from '@vue-storefront/core'
export default {
name: 'DefaultLayout',
components: {
LazyHydrate,
TopBar,
AppHeader,
BottomNavigation,
AppFooter,
CartSidebar,
WishlistSidebar,
FiltersSidebar,
LoginModal,
Notification,
},
setup() {
const { load: loadStores } = useStore()
const { load: loadUser } = useUser()
const { load: loadWishlist } = useWishlist()
const { getLayout, styleGuide } = useCmsLayout()
onSSR(async () => {
await Promise.all([loadStores(), getLayout()])
})
onMounted(async () => {
await Promise.all([loadUser(), loadWishlist()])
})
return {
styleGuide,
}
},
head() {
return this.$nuxtI18nHead({ addSeoAttributes: true })
},
}
</script>
<style lang="scss">
@import '~@storefront-ui/vue/styles';
#layout {
box-sizing: border-box;
@include for-desktop {
max-width: 1240px;
margin: auto;
}
}
.no-scroll {
overflow: hidden;
height: 100vh;
}
// Reset CSS
html {
width: auto;
@include for-mobile {
overflow-x: hidden;
}
}
body {
overflow-x: hidden;
color: var(--c-text);
font-size: var(--font-size--base);
font-family: var(--font-family--primary);
margin: 0;
padding: 0;
}
a {
text-decoration: none;
color: var(--c-link);
&:hover {
color: var(--c-link-hover);
}
}
h1 {
font-family: var(--font-family--secondary);
font-size: var(--h1-font-size);
line-height: 1.6;
margin: 0;
}
h2 {
font-family: var(--font-family--secondary);
font-size: var(--h2-font-size);
line-height: 1.6;
margin: 0;
}
h3 {
font-family: var(--font-family--secondary);
font-size: var(--h3-font-size);
line-height: 1.6;
margin: 0;
}
h4 {
font-family: var(--font-family--secondary);
font-size: var(--h4-font-size);
line-height: 1.6;
margin: 0;
}
</style>
CodePudding user response:
Welcome on stackoverflow!
This error is triggered when a Vue component declares "props" as required, and they are not provided.
The logs says:
Missing required prop height. ------------------- SfImage.vue
So this error is trigger from your SfImage.vue
component.
Hence I guess you declared the height
and width
props from this component as required.
Solution:
- You remove the
required: true
option of your props
OR
- You correctly provide these props when you use that component:
<SfImage height="40px" width="40px" />
CodePudding user response:
The error in the Chrome Dev Tools usually tells you the name of the component you used without providing the required props. In a vue component, props are declared inside it, under the props property (if using vue2), or under declareProps macro (if using vue3)
For more info, you can read about vue component props in the official documentation: https://vuejs.org/guide/components/props.html