Search code examples
javascriptvue.jsgoogle-chromegoogle-chrome-devtoolsdevtools

Using devtools - Missing props. How do I get to what class to look for in my application?


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 class="hero">
        <SfHeroItem
          v-for="(hero, i) in heroes"
          :key="i"
          :title="$t(hero.title)"
          :subtitle="$t(hero.subtitle)"
          :background="hero.background"
          :image="hero.image | addBasePathFilter"
          :class="hero.className"
          :height="200"
          :width="200"
        />
      </SfHero>
    </LazyHydrate>

    <LazyHydrate when-visible>
      <SfBannerGrid :banner-grid="1" class="banner-grid">
        <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"
            :class="item.class"
            :height="200"
            :width="200"
          />
        </template>
      </SfBannerGrid>
    </LazyHydrate>

    <LazyHydrate when-visible>
      <div class="similar-products">
        <SfHeading :title="$t('Match with it')" :level="2" />
        <nuxt-link :to="localePath('/c/women')" class="smartphone-only">See all</nuxt-link>
      </div>
    </LazyHydrate>

    <LazyHydrate when-visible>
      <SfCarousel
        class="carousel"
        :settings="{ peek: 16, breakpoints: { 1023: { peek: 0, perView: 2 } } }"
      >
        <template #prev="{go}">
          <SfArrow
            aria-label="prev"
            class="sf-arrow--left sf-arrow--long"
            @click="go('prev')"
          />
        </template>
        <template #next="{go}">
          <SfArrow
            aria-label="next"
            class="sf-arrow--right sf-arrow--long"
            @click="go('next')"
          />
        </template>
        <SfCarouselItem
          class="carousel__item"
          v-for="(product, i) in products"
          :key="i"
        >
          <SfProductCard
            class="carousel__item__product"
            :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"
        class="call-to-action"
      >
        <template #button>
          <SfButton
            class="sf-call-to-action__button"
            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 class="desktop-only" />
    </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>

Solution

  • 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" />