Skip to content

Commit

Permalink
fix(category): move products loading to onMount hook to avoid caching…
Browse files Browse the repository at this point in the history
… prices (#793)

* refactor(prices): make prices non cachable
- add usePrice composable in catalog module
- rework category page to load prices onMounted lifecycle to avoid caching

* refactor(home): make new product carousel non cachable
- load products data onMounted to avoid price caching

* fix(theme): mobile store banner
- replace SfButton with SfLink so we can have images inside elements without breaking hydration

* refactor(theme): change the way related and upsell products are loaded
- upsell/related products on the PDP will be now loaded onMounted hook

M2-306
  • Loading branch information
bartoszherba authored and Frodigo committed May 4, 2022
1 parent 2c7c501 commit e9c2bc1
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 24 deletions.
14 changes: 8 additions & 6 deletions packages/theme/components/MobileStoreBanner.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
>
<template #call-to-action>
<div class="app-banner__call-to-action">
<SfButton
<SfLink
class="app-banner__button sf-banner__call-to-action"
aria-label="Go to Apple Product"
data-testid="banner-cta-button"
link="#"
>
<SfImage
src="/homepage/apple.png"
Expand All @@ -19,11 +20,12 @@
:width="134"
:height="44"
/>
</SfButton>
<SfButton
</SfLink>
<SfLink
class="app-banner__button sf-banner__call-to-action"
aria-label="Go to Google Product"
data-testid="banner-cta-button"
link="#"
>
<SfImage
src="/homepage/google.png"
Expand All @@ -32,15 +34,15 @@
:width="134"
:height="44"
/>
</SfButton>
</SfLink>
</div>
</template>
</SfBanner>
</template>
<script type="module">
import {
SfBanner,
SfButton,
SfLink,
SfImage,
} from '@storefront-ui/vue';
import { defineComponent } from '@nuxtjs/composition-api';
Expand All @@ -49,7 +51,7 @@ export default defineComponent({
name: 'AppStoreBanner',
components: {
SfBanner,
SfButton,
SfLink,
SfImage,
},
});
Expand Down
6 changes: 4 additions & 2 deletions packages/theme/components/RelatedProducts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
/>
</template>
<script>
import { defineComponent, useFetch, ref } from '@nuxtjs/composition-api';
import {
defineComponent, ref, onMounted,
} from '@nuxtjs/composition-api';
import { useRelatedProducts } from '~/composables';
import ProductsCarousel from '~/components/ProductsCarousel.vue';
import { productData } from '~/helpers/product/productData';
Expand All @@ -24,7 +26,7 @@ export default defineComponent({
} = useRelatedProducts();
const products = ref([]);
useFetch(async () => {
onMounted(async () => {
const baseSearchQuery = {
filter: {
sku: {
Expand Down
4 changes: 2 additions & 2 deletions packages/theme/components/UpsellProducts.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
/>
</template>
<script>
import { defineComponent, useFetch, ref } from '@nuxtjs/composition-api';
import { defineComponent, ref, onMounted } from '@nuxtjs/composition-api';
import { useUpsellProducts } from '~/composables';
import ProductsCarousel from '~/components/ProductsCarousel.vue';
import { productData } from '~/helpers/product/productData';
Expand All @@ -21,7 +21,7 @@ export default defineComponent({
const { search, loading } = useUpsellProducts(id);
const products = ref([]);
useFetch(async () => {
onMounted(async () => {
const baseSearchQuery = {
filter: {
sku: {
Expand Down
6 changes: 6 additions & 0 deletions packages/theme/composables/useApi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ export const useApi = () => {
const { app } = useContext();
const customerToken = app.$cookies.get(cookieNames.customerCookieName);
const storeCode = app.$cookies.get(cookieNames.storeCookieName);
const currency = app.$cookies.get(cookieNames.currencyCookieName);
const magentoConfig = app.$vsf.$magento.config;
// TODO remove once we remove apollo client
const { useGETForQueries } = magentoConfig.customApolloHttpLinkOptions;
const defaultEndpoint = magentoConfig.magentoApiEndpoint;
const defaultHeaders: {
authorization?: string,
store?: string
'Content-Currency'?: string
} = {};

if (customerToken) {
Expand All @@ -23,6 +25,10 @@ export const useApi = () => {
defaultHeaders.store = storeCode;
}

if (currency) {
defaultHeaders['Content-Currency'] = currency;
}

const query = (
document: string,
variables = null,
Expand Down
40 changes: 32 additions & 8 deletions packages/theme/modules/catalog/pages/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,16 @@
class="products__product-card"
@click:wishlist="addItemToWishlist(product)"
@click:add-to-cart="addItemToCart({ product, quantity: 1 })"
/>
>
<template #price>
<SfPrice
:class="{ 'display-none': !isPriceLoaded || !$fc(getPrice(product).regular) }"
class="sf-product-card__price"
:regular="$fc(getPrice(product).regular)"
:special="getPrice(product).special && $fc(getPrice(product).special)"
/>
</template>
</SfProductCard>
</transition-group>
<transition-group
v-else
Expand Down Expand Up @@ -312,18 +321,17 @@ import {
SfColor,
SfFilter,
SfHeading,
SfLink,
SfPagination,
SfProductCard,
SfProductCardHorizontal,
SfProperty,
SfRadio,
SfSelect,
SfSidebar,
SfImage,
SfPrice,
} from '@storefront-ui/vue';
import {
defineComponent, ref, useContext, useFetch,
defineComponent, onMounted, ref, useContext, ssrRef, useFetch,
} from '@nuxtjs/composition-api';
import { CacheTagPrefix, useCache } from '@vue-storefront/cache';
import { facetGetters, productGetters } from '~/getters';
Expand All @@ -341,6 +349,7 @@ import { useAddToCart } from '~/helpers/cart/addToCart';
import { useCategoryContent } from '~/modules/catalog/category/components/cms/useCategoryContent.ts';
import CategoryNavbar from '~/modules/catalog/category/components/navbar/CategoryNavbar';
import SkeletonLoader from '~/components/SkeletonLoader';
import { usePrice } from '~/modules/catalog/pricing/usePrice.ts';
// TODO(addToCart qty, horizontal): https://github.com/vuestorefront/storefront-ui/issues/1606
export default defineComponent({
Expand All @@ -351,6 +360,7 @@ export default defineComponent({
CmsContent: () => import('~/modules/catalog/category/components/cms/CmsContent'),
CategorySidebar: () => import('~/modules/catalog/category/components/sidebar/CategorySidebar'),
EmptyResults: () => import('~/modules/catalog/category/components/EmptyResults'),
SfPrice,
SfButton,
SfSidebar,
SfFilter,
Expand All @@ -363,9 +373,7 @@ export default defineComponent({
SfColor,
SfHeading,
SfProperty,
SfLink,
LazyHydrate,
SfImage,
},
middleware: cacheControl({
'max-age': 60,
Expand All @@ -381,7 +389,7 @@ export default defineComponent({
const cmsContent = ref('');
const isShowCms = ref(false);
const isShowProducts = ref(false);
const products = ref([]);
const products = ssrRef([]);
const sortBy = ref({});
const facets = ref([]);
const pagination = ref({});
Expand Down Expand Up @@ -488,13 +496,13 @@ export default defineComponent({
await searchCategoryProduct(routeData?.entity_uid);
selectedFilters.value = getSelectedFilterValues();
products.value = facetGetters.getProducts(result.value) ?? [];
sortBy.value = facetGetters.getSortOptions(result.value);
facets.value = facetGetters.getGrouped(
result.value,
magentoConfig.facets.available,
);
pagination.value = facetGetters.getPagination(result.value);
const tags = [{ prefix: CacheTagPrefix.View, value: 'category' }];
// eslint-disable-next-line no-underscore-dangle
const productTags = products.value.map((product) => ({
Expand All @@ -506,9 +514,25 @@ export default defineComponent({
addTags([...tags, ...productTags]);
});
const isPriceLoaded = ref(false);
onMounted(async () => {
const { getPricesBySku } = usePrice();
if (products.value.length > 0) {
const skus = products.value.map((item) => item.sku);
const priceData = await getPricesBySku(skus);
products.value = products.value.map((product) => {
const modifiedProduct = { ...product };
modifiedProduct.price_range = priceData.items.find((item) => item.sku === product.sku).price_range;
return modifiedProduct;
});
}
isPriceLoaded.value = true;
});
const { getMagentoImage, imageSizes } = useImage();
return {
isPriceLoaded,
...productGetters,
...uiHelpers,
...uiState,
Expand Down
48 changes: 48 additions & 0 deletions packages/theme/modules/catalog/pricing/__tests__/usePrice.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { usePrice } from '~/modules/catalog/pricing/usePrice';
import { useApi } from '~/composables/useApi';
import getPricesQuery from '~/modules/catalog/pricing/getPricesQuery.gql';

jest.mock('~/composables/useApi', () => ({
useApi: jest.fn(),
}));

describe('usePrice', () => {
it('Factory returns required methods', () => {
const actual = usePrice();
expect(actual).toHaveProperty('getPricesBySku');
expect(actual).toHaveProperty('getPrices');
});

it('getPrices', async () => {
const { getPrices } = usePrice();
const pageSize = 20;
const currentPage = 1;
const variables = { filter: { sku: { eq: 'test' } }, pageSize, currentPage };
const expectedResult = { items: [] };
const useApiMock = { query: jest.fn(() => (expectedResult)) };

useApi.mockReturnValue(useApiMock);
const actualResult = await getPrices(variables);

expect(useApiMock.query).toBeCalledTimes(1);
expect(useApiMock.query).toBeCalledWith(getPricesQuery, variables);
expect(expectedResult).toMatchObject(actualResult);
});

it('getPricesBySku', async () => {
const { getPricesBySku } = usePrice();
const pageSize = 20;
const currentPage = 1;
const skus = ['sku1', 'sku2'];
const expectedVariables = { filter: { sku: { in: skus } }, pageSize, currentPage };
const expectedResult = { items: [] };
const useApiMock = { query: jest.fn(() => (expectedResult)) };

useApi.mockReturnValue(useApiMock);
const actualResult = await getPricesBySku(skus, pageSize, currentPage);

expect(useApiMock.query).toBeCalledTimes(1);
expect(useApiMock.query).toBeCalledWith(getPricesQuery, expectedVariables);
expect(expectedResult).toMatchObject(actualResult);
});
});
33 changes: 33 additions & 0 deletions packages/theme/modules/catalog/pricing/getPricesQuery.gql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { gql } from 'graphql-request';

export default gql`
query productsList($search: String = "", $filter: ProductAttributeFilterInput, $pageSize: Int = 20, $currentPage: Int = 1, $sort: ProductAttributeSortInput) {
products(search: $search, filter: $filter, pageSize: $pageSize, currentPage: $currentPage, sort: $sort) {
items {
sku
price_range {
maximum_price {
final_price {
currency
value
}
regular_price {
currency
value
}
}
minimum_price {
final_price {
currency
value
}
regular_price {
currency
value
}
}
}
}
}
}
`;
28 changes: 28 additions & 0 deletions packages/theme/modules/catalog/pricing/usePrice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { useApi } from '~/composables/useApi';
import getPricesQuery from '~/modules/catalog/pricing/getPricesQuery.gql';
import { PriceRange } from '~/modules/GraphQL/types';
import { GetProductSearchParams } from '~/composables/types';

export interface PriceItem {
price_range: PriceRange;
sku: String;
}

export interface PriceItems {
items: PriceItem[]
}

export const usePrice = () => {
const getPrices = async (variables: GetProductSearchParams): Promise<PriceItems> => {
const { query } = useApi();
const { products } = await query(getPricesQuery, variables);

return products ?? { items: [] };
};

const getPricesBySku = async (skus: string[], pageSize = 20, currentPage = 1): Promise<PriceItems> => getPrices(
{ filter: { sku: { in: skus } }, pageSize, currentPage },
);

return { getPricesBySku, getPrices };
};
11 changes: 5 additions & 6 deletions packages/theme/pages/Home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,10 @@ import {
} from '@storefront-ui/vue';
import {
computed,
defineComponent,
ref,
useContext,
useAsync,
onMounted,
} from '@nuxtjs/composition-api';
import LazyHydrate from 'vue-lazy-hydration';
import { useCache, CacheTagPrefix } from '@vue-storefront/cache';
Expand Down Expand Up @@ -134,6 +133,7 @@ export default defineComponent({
const { app } = useContext();
const year = new Date().getFullYear();
const { getProductList, loading: newProductsLoading } = useProduct();
const newProducts = ref([]);
const heroes = ref([
{
title: app.i18n.t('Colorful summer dresses are already in store'),
Expand Down Expand Up @@ -240,7 +240,7 @@ export default defineComponent({
},
]);
const products = useAsync(async () => {
onMounted(async () => {
const productsData = await getProductList({
pageSize: 10,
currentPage: 1,
Expand All @@ -250,12 +250,11 @@ export default defineComponent({
});
addTags([{ prefix: CacheTagPrefix.View, value: 'home' }]);
return productsData;
newProducts.value = productGetters.getFiltered(productsData?.items, { master: true });
});
// @ts-ignore
const newProducts = computed(() => productGetters.getFiltered(products.value?.items, { master: true }));
return {
banners,
heroes,
Expand Down

0 comments on commit e9c2bc1

Please sign in to comment.