# useProduct

# Features

useProduct composable is responsible for fetching a list of products.

# API

  • search - the main querying function used to query products from the eCommerce platform and populate the products object with the result. Every time you invoke this function API request is made. This method accepts a single params object. The params has the following options:

    • searchParams: ProductsSearchParams

    • customQuery?: CustomQuery

    • enhanceProduct?: EnhanceProduct

      interface ProductsSearchParams {
        perPage?: number;
        page?: number;
        sort?: any;
        term?: any;
        filters?: Record<string, Filter>;
        catId?: string | string[];
        skus?: string[];
        slug?: string;
        id?: string;
        ids?: string[];
        customFilters?: string;
      }
      
      type CustomQuery = {
        products: string
      }
      
      type EnhanceProduct = (
        productResponse: ApolloQueryResult<ProductData>,
        context: Context
      ) => ApolloQueryResult<ProductData>
      

      Option customFilters can contain a custom string with all the filters you want. Please refer to commercetools documentation (opens new window) to learn more about query predicates.

  • products: ProductVariant[] - the main data object that contains an array of products fetched by search method.

    type ProductVariant = {
      __typename?: "ProductVariant";
      id: Scalars["Int"];
      key?: Maybe<Scalars["String"]>;
      sku?: Maybe<Scalars["String"]>;
      prices?: Maybe<Array<ProductPrice>>;
      price?: Maybe<ProductPrice>;
      images: Array<Image>;
      assets: Array<Asset>;
      availability?: Maybe<ProductVariantAvailabilityWithChannels>;
      attributesRaw: Array<RawProductAttribute>;
      attributes: ProductType;
      attributeList: Array<Attribute>;
    }
    
  • loading: boolean - a reactive object containing information about the loading state of your search method.

  • error: UseProductErrors - reactive object containing the error message, if search failed for any reason.

    interface UseProductErrors {
      search: Error;
    }
    

# Getters

  • getName - returns product name.

  • getSlug - returns product slug.

  • getPrice - returns product price.

  • getGallery - returns product gallery.

  • getCoverImage - returns cover image of product.

  • getFiltered - returns filtered product.

  • getAttributes - returns product attributes.

  • getDescription - returns product description.

  • getCategoryIds - returns all product categories IDs.

  • getCategorySlugs - returns all product categories slugs.

  • getId - returns product ID.

  • getSku - returns product sku.

  • getFormattedPrice - returns product price with currency sign.

  • getTotalReviews - returns total number of reviews product has.

  • getAverageRating - returns average rating from all reviews.

  • getBreadcrumbs - returns breadcrumbs.

    interface ProductGetters {
      getName: (product: ProductVariant | Readonly<ProductVariant>) => string;
      getSlug: (product: ProductVariant | Readonly<ProductVariant>) => string;
      getPrice: (product: ProductVariant | Readonly<ProductVariant>) => AgnosticPrice;
      getGallery: (product: ProductVariant) => AgnosticMediaGalleryItem[];
      getCoverImage: (product: ProductVariant) => string;
      getFiltered: (products: ProductVariant[], filters: ProductVariantFilters | any = {}) => ProductVariant[];
      getAttributes: (products: ProductVariant[] | ProductVariant, filterByAttributeName?: string[]) => Record<string, AgnosticAttribute | string>;
      getDescription: (product: ProductVariant) => string;
      getCategoryIds: (product: ProductVariant) => string[];
      getCategorySlugs: (product: ProductVariant) => string[];
      getId: (product: ProductVariant) => string;
      getSku: (product: ProductVariant) => any;
      getFormattedPrice: (price: number) => string;
      getTotalReviews: (product: ProductVariant) => number;
      getAverageRating: (product: ProductVariant) => number;
      getBreadcrumbs: (product: Record<string, any>) => AgnosticBreadcrumb[];
    }
    
    interface AgnosticPrice {
      regular: number | null;
      special?: number | null;
    }
    
    interface AgnosticMediaGalleryItem {
      small: string;
      normal: string;
      big: string;
    }
    
    interface AgnosticAttribute {
      name?: string;
      value: string | Record<string, any>;
      label: string;
    }
    
    type ProductVariant = {
      __typename?: "ProductVariant";
      id: Scalars["Int"];
      key?: Maybe<Scalars["String"]>;
      sku?: Maybe<Scalars["String"]>;
      prices?: Maybe<Array<ProductPrice>>;
      price?: Maybe<ProductPrice>;
      images: Array<Image>;
      assets: Array<Asset>;
      availability?: Maybe<ProductVariantAvailabilityWithChannels>;
      attributesRaw: Array<RawProductAttribute>;
      attributes: ProductType;
      attributeList: Array<Attribute>;
    }
    
    interface ProductVariantFilters {
      master?: boolean;
      attributes?: Record<string, string>;
    }
    

# Examples

// search single product
import { computed } from '@nuxtjs/composition-api';
import { useProduct, productGetters } from '@vsf-enterprise/commercetools';
import { onSSR } from '@vue-storefront/core';

export default {
  setup () {
    const { products, search, loading, error } = useProduct('<UNIQUE_ID>');

    onSSR(async () => {
      await search({ slug: 'super-t-shirt' })
    })

    return {
      loading,
      error,
      product: computed(() => productGetters.getFiltered(products.value, { master: true, attributes: context.root.$route.query })[0]),
      option: computed(() => productGetters.getAttributes(products.value, ['color', 'size'])),
      configuration: computed(() => productGetters.getCategoryIds(product.value))
    }
  }
}
// search products by ids
import { computed } from '@nuxtjs/composition-api';
import { useProduct, productGetters } from '@vsf-enterprise/commercetools';
import { onSSR } from '@vue-storefront/core';

export default {
  setup () {
    const { products, search } = useProduct('<UNIQUE_ID>');

    onSSR(async () => {
      await search({ ids: ['id-1', 'id-2'] });
    });

    return {
      products,
      masterProducts: computed(() => productGetters.getFiltered(products.value, { master: true }))
    };
  }
}

# Using enhanceProduct

Under the hood the search method of the useProduct composable uses the enhanceProduct function to model the returned data. The function looks as follows:

import type { ApolloQueryResult } from '@apollo/client/core';
import type { ProductQueryResult } from '@vsf-enterprise/commercetools-types';
import type { Context } from '@vue-storefront/core';

interface ProductData {
  products: ProductQueryResult;
}

const getTranslated = (rawAttribute, context) => {
  const { locale } = context.$ct.config;
  if (rawAttribute.attributeDefinition.type.name === 'ltext') {
    return rawAttribute.value[locale];
  }
  if (rawAttribute.attributeDefinition.type.name === 'lenum') {
    return rawAttribute.value.label[locale];
  }
  return rawAttribute.value;
};

export type EnhanceProduct = (productResponse: ApolloQueryResult<ProductData>, context: Context) => ApolloQueryResult<ProductData>;

export const enhanceProduct: EnhanceProduct = (productResponse, context) => {
  (productResponse.data as any)._variants = productResponse.data.products.results
    .map((product) => {
      const current = product.masterData.current;

      return current.allVariants.map((variant) => ({
        ...variant,
        attributesRaw: variant.attributesRaw.map((raw) => ({
          ...raw,
          _translated: getTranslated(raw, context)
        })),
        _name: current.name,
        _slug: current.slug,
        _id: product.id,
        _key: product.key,
        _master: current.masterVariant.id === variant.id,
        _description: current.description,
        _categoriesRef: current.categoriesRef.map((cr) => cr.id),
        _categories: current.categories.map(category => ({ id: category.id, name: category.name, slug: category.slug })),
        _rating: (product as any).reviewRatingStatistics,
        _original: product
      }));
    })
    .reduce((prev, curr) => [...prev, ...curr], []);

  return productResponse;
};

You can replace the original enhanceProduct function when calling the search method from the useProduct composable by passing your own enhanceProduct function as an argument. This can be very useful if you're working with many variants and want to slim down the returned object or want to best tailor the returned data to your specific use case.

import { useProduct } from '@vue-storefront/commercetools';
import { onSSR } from '@vue-storefront/core';
import { myEnhanceProduct } from './myEnhanceProduct.ts';

export default {
  setup () {
    const { products, search } = useProduct('<UNIQUE_ID>');

    onSSR(async () => {
      await search({ id: 'product-id', enhanceProduct: myEnhanceProduct });
    });

    return {
      products,
    };
  }
}