From 800d598d61f26f13d157832c0eed41ce21198ba6 Mon Sep 17 00:00:00 2001 From: Sebastian Holesz Date: Tue, 14 Nov 2023 15:48:14 +0100 Subject: [PATCH] added logic for orgering GTM events - this is handled by a global context where we have a boolean pointer making the rest of the app aware of the fact if page view has run already - this state is reset on route change start - documentation for this mechanism has been added --- .../components/Pages/App/AppPageContent.tsx | 7 ++-- storefront/gtm/context/GtmProvider.tsx | 33 +++++++++++++++++++ storefront/gtm/context/useGtmContext.ts | 13 ++++++++ .../useGtmPaginatedProductListViewEvent.ts | 6 ++-- .../useGtmSliderProductListViewEvent.ts | 6 ++-- .../useGtmAutocompleteResultsViewEvent.ts | 6 ++-- storefront/gtm/hooks/useGtmCartViewEvent.ts | 5 ++- .../useGtmContactInformationPageViewEvent.ts | 6 ++-- storefront/gtm/hooks/useGtmPageViewEvent.ts | 3 ++ .../useGtmPaymentAndTransportPageViewEvent.ts | 5 ++- .../gtm/hooks/useGtmProductDetailViewEvent.ts | 6 ++-- 11 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 storefront/gtm/context/GtmProvider.tsx create mode 100644 storefront/gtm/context/useGtmContext.ts diff --git a/storefront/components/Pages/App/AppPageContent.tsx b/storefront/components/Pages/App/AppPageContent.tsx index c853f28c04..309d8309ed 100644 --- a/storefront/components/Pages/App/AppPageContent.tsx +++ b/storefront/components/Pages/App/AppPageContent.tsx @@ -1,6 +1,7 @@ import { Fonts } from './Fonts'; import { Error503Content } from 'components/Pages/ErrorPage/Error503Content'; import { GtmHeadScript } from 'gtm/GtmHeadScript'; +import { GtmProvider } from 'gtm/context/GtmProvider'; import { getInternationalizedStaticUrls } from 'helpers/getInternationalizedStaticUrls'; import { ServerSidePropsType } from 'helpers/serverSide/initServerSideProps'; import { useAuthLoader } from 'hooks/app/useAuthLoader'; @@ -47,8 +48,10 @@ export const AppPageContent: FC = ({ Component, pageProps }
- {!userConsent && !isConsentUpdatePage && } - {pageProps.isMaintenance ? : } + + {!userConsent && !isConsentUpdatePage && } + {pageProps.isMaintenance ? : } + ); }; diff --git a/storefront/gtm/context/GtmProvider.tsx b/storefront/gtm/context/GtmProvider.tsx new file mode 100644 index 0000000000..a3d938929f --- /dev/null +++ b/storefront/gtm/context/GtmProvider.tsx @@ -0,0 +1,33 @@ +import { useRouter } from 'next/router'; +import React, { useState, createContext, useEffect } from 'react'; + +export type GtmContextType = { + didPageViewRun: boolean; + setDidPageViewRun: (newState: boolean) => void; +}; + +const defaultState: GtmContextType = { + didPageViewRun: false, + setDidPageViewRun: () => undefined, +}; + +export const GtmContext = createContext(defaultState); + +export const GtmProvider: FC = ({ children }) => { + const [didPageViewRun, setDidPageViewRun] = useState(defaultState.didPageViewRun); + const router = useRouter(); + + useEffect(() => { + const onRouteChangeStart = () => { + setDidPageViewRun(false); + }; + + router.events.on('routeChangeStart', onRouteChangeStart); + + return () => { + router.events.off('routeChangeStart', onRouteChangeStart); + }; + }, [router.events]); + + return {children}; +}; diff --git a/storefront/gtm/context/useGtmContext.ts b/storefront/gtm/context/useGtmContext.ts new file mode 100644 index 0000000000..d0141611d4 --- /dev/null +++ b/storefront/gtm/context/useGtmContext.ts @@ -0,0 +1,13 @@ +import { GtmContext, GtmContextType } from './GtmProvider'; +import { useContext } from 'react'; + +export const useGtmContext = (): GtmContextType => { + const context = useContext(GtmContext); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (!context) { + throw new Error('useGtmContext must be used within a GtmProvider'); + } + + return context; +}; diff --git a/storefront/gtm/hooks/productList/useGtmPaginatedProductListViewEvent.ts b/storefront/gtm/hooks/productList/useGtmPaginatedProductListViewEvent.ts index 3e92a3541b..53b9a7c866 100644 --- a/storefront/gtm/hooks/productList/useGtmPaginatedProductListViewEvent.ts +++ b/storefront/gtm/hooks/productList/useGtmPaginatedProductListViewEvent.ts @@ -1,5 +1,6 @@ import { DEFAULT_PAGE_SIZE } from 'config/constants'; import { ListedProductFragmentApi } from 'graphql/generated'; +import { useGtmContext } from 'gtm/context/useGtmContext'; import { getGtmProductListViewEvent } from 'gtm/helpers/eventFactories'; import { gtmSafePushEvent } from 'gtm/helpers/gtm'; import { GtmProductListNameType } from 'gtm/types/enums'; @@ -16,9 +17,10 @@ export const useGtmPaginatedProductListViewEvent = ( const previousLoadMoreRef = useRef(currentLoadMore); const { url } = useDomainConfig(); const stringifiedProducts = JSON.stringify(paginatedProducts); + const { didPageViewRun } = useGtmContext(); useEffect(() => { - if (paginatedProducts && lastViewedStringifiedProducts.current !== stringifiedProducts) { + if (didPageViewRun && paginatedProducts && lastViewedStringifiedProducts.current !== stringifiedProducts) { lastViewedStringifiedProducts.current = stringifiedProducts; let paginatedProductsSlice = paginatedProducts; @@ -37,5 +39,5 @@ export const useGtmPaginatedProductListViewEvent = ( ), ); } - }, [gtmProductListName, currentPage, url, currentLoadMore, stringifiedProducts]); + }, [gtmProductListName, currentPage, url, currentLoadMore, stringifiedProducts, didPageViewRun]); }; diff --git a/storefront/gtm/hooks/productList/useGtmSliderProductListViewEvent.ts b/storefront/gtm/hooks/productList/useGtmSliderProductListViewEvent.ts index cb23b5b57f..a6b005169c 100644 --- a/storefront/gtm/hooks/productList/useGtmSliderProductListViewEvent.ts +++ b/storefront/gtm/hooks/productList/useGtmSliderProductListViewEvent.ts @@ -1,4 +1,5 @@ import { ListedProductFragmentApi } from 'graphql/generated'; +import { useGtmContext } from 'gtm/context/useGtmContext'; import { getGtmProductListViewEvent } from 'gtm/helpers/eventFactories'; import { gtmSafePushEvent } from 'gtm/helpers/gtm'; import { GtmProductListNameType } from 'gtm/types/enums'; @@ -11,11 +12,12 @@ export const useGtmSliderProductListViewEvent = ( ): void => { const wasViewedRef = useRef(false); const { url } = useDomainConfig(); + const { didPageViewRun } = useGtmContext(); useEffect(() => { - if (products?.length && !wasViewedRef.current) { + if (didPageViewRun && products?.length && !wasViewedRef.current) { wasViewedRef.current = true; gtmSafePushEvent(getGtmProductListViewEvent(products, gtmProuctListName, 1, 0, url)); } - }, [gtmProuctListName, products, url]); + }, [gtmProuctListName, products, url, didPageViewRun]); }; diff --git a/storefront/gtm/hooks/useGtmAutocompleteResultsViewEvent.ts b/storefront/gtm/hooks/useGtmAutocompleteResultsViewEvent.ts index b1943b025a..25bb68d2a6 100644 --- a/storefront/gtm/hooks/useGtmAutocompleteResultsViewEvent.ts +++ b/storefront/gtm/hooks/useGtmAutocompleteResultsViewEvent.ts @@ -1,4 +1,5 @@ import { AutocompleteSearchQueryApi } from 'graphql/generated'; +import { useGtmContext } from 'gtm/context/useGtmContext'; import { getGtmAutocompleteResultsViewEvent } from 'gtm/helpers/eventFactories'; import { gtmSafePushEvent } from 'gtm/helpers/gtm'; import { useEffect, useRef } from 'react'; @@ -8,11 +9,12 @@ export const useGtmAutocompleteResultsViewEvent = ( searchQuery: string, ): void => { const lastViewedAutocompleteResults = useRef(); + const { didPageViewRun } = useGtmContext(); useEffect(() => { - if (searchResults !== undefined && lastViewedAutocompleteResults.current !== searchResults) { + if (didPageViewRun && searchResults !== undefined && lastViewedAutocompleteResults.current !== searchResults) { lastViewedAutocompleteResults.current = searchResults; gtmSafePushEvent(getGtmAutocompleteResultsViewEvent(searchResults, searchQuery)); } - }, [searchResults, searchQuery]); + }, [searchResults, searchQuery, didPageViewRun]); }; diff --git a/storefront/gtm/hooks/useGtmCartViewEvent.ts b/storefront/gtm/hooks/useGtmCartViewEvent.ts index 653a08f1b2..f6b3b9ba0c 100644 --- a/storefront/gtm/hooks/useGtmCartViewEvent.ts +++ b/storefront/gtm/hooks/useGtmCartViewEvent.ts @@ -1,3 +1,4 @@ +import { useGtmContext } from 'gtm/context/useGtmContext'; import { getGtmCartViewEvent } from 'gtm/helpers/eventFactories'; import { gtmSafePushEvent } from 'gtm/helpers/gtm'; import { GtmPageViewEventType } from 'gtm/types/events'; @@ -6,9 +7,11 @@ import { useEffect, useRef } from 'react'; export const useGtmCartViewEvent = (gtmPageViewEvent: GtmPageViewEventType): void => { const wasViewedRef = useRef(false); const previousPromoCodes = useRef(JSON.stringify(gtmPageViewEvent.cart?.promoCodes)); + const { didPageViewRun } = useGtmContext(); useEffect(() => { if ( + didPageViewRun && gtmPageViewEvent._isLoaded && gtmPageViewEvent.cart !== undefined && gtmPageViewEvent.cart !== null && @@ -25,5 +28,5 @@ export const useGtmCartViewEvent = (gtmPageViewEvent: GtmPageViewEventType): voi ), ); } - }, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart, gtmPageViewEvent.currencyCode]); + }, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart, gtmPageViewEvent.currencyCode, didPageViewRun]); }; diff --git a/storefront/gtm/hooks/useGtmContactInformationPageViewEvent.ts b/storefront/gtm/hooks/useGtmContactInformationPageViewEvent.ts index 9f091ca01b..b0aa93d15a 100644 --- a/storefront/gtm/hooks/useGtmContactInformationPageViewEvent.ts +++ b/storefront/gtm/hooks/useGtmContactInformationPageViewEvent.ts @@ -1,3 +1,4 @@ +import { useGtmContext } from 'gtm/context/useGtmContext'; import { getGtmContactInformationPageViewEvent } from 'gtm/helpers/eventFactories'; import { gtmSafePushEvent } from 'gtm/helpers/gtm'; import { GtmPageViewEventType } from 'gtm/types/events'; @@ -5,11 +6,12 @@ import { useEffect, useRef } from 'react'; export const useGtmContactInformationPageViewEvent = (gtmPageViewEvent: GtmPageViewEventType): void => { const wasViewedRef = useRef(false); + const { didPageViewRun } = useGtmContext(); useEffect(() => { - if (gtmPageViewEvent._isLoaded && gtmPageViewEvent.cart && !wasViewedRef.current) { + if (didPageViewRun && gtmPageViewEvent._isLoaded && gtmPageViewEvent.cart && !wasViewedRef.current) { wasViewedRef.current = true; gtmSafePushEvent(getGtmContactInformationPageViewEvent(gtmPageViewEvent.cart)); } - }, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart]); + }, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart, didPageViewRun]); }; diff --git a/storefront/gtm/hooks/useGtmPageViewEvent.ts b/storefront/gtm/hooks/useGtmPageViewEvent.ts index 1b28b7de51..5a534e5bc5 100644 --- a/storefront/gtm/hooks/useGtmPageViewEvent.ts +++ b/storefront/gtm/hooks/useGtmPageViewEvent.ts @@ -1,3 +1,4 @@ +import { useGtmContext } from 'gtm/context/useGtmContext'; import { gtmSafePushEvent } from 'gtm/helpers/gtm'; import { GtmPageViewEventType } from 'gtm/types/events'; import { getUrlWithoutGetParameters } from 'helpers/parsing/urlParsing'; @@ -8,11 +9,13 @@ export const useGtmPageViewEvent = (gtmPageViewEvent: GtmPageViewEventType, fetc const router = useRouter(); const slug = getUrlWithoutGetParameters(router.asPath); const lastViewedSlug = useRef(); + const { setDidPageViewRun } = useGtmContext(); useEffect(() => { if (gtmPageViewEvent._isLoaded && lastViewedSlug.current !== slug && !fetching) { lastViewedSlug.current = slug; gtmSafePushEvent(gtmPageViewEvent); + setDidPageViewRun(true); } }, [gtmPageViewEvent, fetching, slug]); }; diff --git a/storefront/gtm/hooks/useGtmPaymentAndTransportPageViewEvent.ts b/storefront/gtm/hooks/useGtmPaymentAndTransportPageViewEvent.ts index dccab6861a..bef0507abc 100644 --- a/storefront/gtm/hooks/useGtmPaymentAndTransportPageViewEvent.ts +++ b/storefront/gtm/hooks/useGtmPaymentAndTransportPageViewEvent.ts @@ -1,3 +1,4 @@ +import { useGtmContext } from 'gtm/context/useGtmContext'; import { getGtmPaymentAndTransportPageViewEvent } from 'gtm/helpers/eventFactories'; import { gtmSafePushEvent } from 'gtm/helpers/gtm'; import { GtmPageViewEventType } from 'gtm/types/events'; @@ -5,9 +6,11 @@ import { useEffect, useRef } from 'react'; export const useGtmPaymentAndTransportPageViewEvent = (gtmPageViewEvent: GtmPageViewEventType): void => { const wasViewedRef = useRef(false); + const { didPageViewRun } = useGtmContext(); useEffect(() => { if ( + didPageViewRun && gtmPageViewEvent._isLoaded && gtmPageViewEvent.cart !== null && gtmPageViewEvent.cart !== undefined && @@ -18,5 +21,5 @@ export const useGtmPaymentAndTransportPageViewEvent = (gtmPageViewEvent: GtmPage getGtmPaymentAndTransportPageViewEvent(gtmPageViewEvent.cart.currencyCode, gtmPageViewEvent.cart), ); } - }, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart]); + }, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart, didPageViewRun]); }; diff --git a/storefront/gtm/hooks/useGtmProductDetailViewEvent.ts b/storefront/gtm/hooks/useGtmProductDetailViewEvent.ts index 3dbea8f99b..756bea523c 100644 --- a/storefront/gtm/hooks/useGtmProductDetailViewEvent.ts +++ b/storefront/gtm/hooks/useGtmProductDetailViewEvent.ts @@ -1,4 +1,5 @@ import { MainVariantDetailFragmentApi, ProductDetailFragmentApi } from 'graphql/generated'; +import { useGtmContext } from 'gtm/context/useGtmContext'; import { getGtmProductDetailViewEvent } from 'gtm/helpers/eventFactories'; import { gtmSafePushEvent } from 'gtm/helpers/gtm'; import { useDomainConfig } from 'hooks/useDomainConfig'; @@ -11,11 +12,12 @@ export const useGtmProductDetailViewEvent = ( ): void => { const lastViewedProductDetailSlug = useRef(undefined); const { url, currencyCode } = useDomainConfig(); + const { didPageViewRun } = useGtmContext(); useEffect(() => { - if (lastViewedProductDetailSlug.current !== slug && !fetching) { + if (didPageViewRun && lastViewedProductDetailSlug.current !== slug && !fetching) { lastViewedProductDetailSlug.current = slug; gtmSafePushEvent(getGtmProductDetailViewEvent(productDetailData, currencyCode, url)); } - }, [productDetailData, currencyCode, slug, url, fetching]); + }, [productDetailData, currencyCode, slug, url, fetching, didPageViewRun]); };