Skip to content

Commit

Permalink
added logic for orgering GTM events
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
sebaholesz committed Nov 16, 2023
1 parent ef5055b commit 800d598
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 14 deletions.
7 changes: 5 additions & 2 deletions storefront/components/Pages/App/AppPageContent.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -47,8 +48,10 @@ export const AppPageContent: FC<AppPageContentProps> = ({ Component, pageProps }
<Fonts />
<div className="absolute left-0 top-0 z-overlay h-[1px] w-[1px]" id="portal" />
<ToastContainer autoClose={6000} position="top-center" theme="colored" />
{!userConsent && !isConsentUpdatePage && <UserConsentContainer />}
{pageProps.isMaintenance ? <Error503Content /> : <Component {...pageProps} />}
<GtmProvider>
{!userConsent && !isConsentUpdatePage && <UserConsentContainer />}
{pageProps.isMaintenance ? <Error503Content /> : <Component {...pageProps} />}
</GtmProvider>
</>
);
};
33 changes: 33 additions & 0 deletions storefront/gtm/context/GtmProvider.tsx
Original file line number Diff line number Diff line change
@@ -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 <GtmContext.Provider value={{ didPageViewRun, setDidPageViewRun }}>{children}</GtmContext.Provider>;
};
13 changes: 13 additions & 0 deletions storefront/gtm/context/useGtmContext.ts
Original file line number Diff line number Diff line change
@@ -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;
};
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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;
Expand All @@ -37,5 +39,5 @@ export const useGtmPaginatedProductListViewEvent = (
),
);
}
}, [gtmProductListName, currentPage, url, currentLoadMore, stringifiedProducts]);
}, [gtmProductListName, currentPage, url, currentLoadMore, stringifiedProducts, didPageViewRun]);
};
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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]);
};
6 changes: 4 additions & 2 deletions storefront/gtm/hooks/useGtmAutocompleteResultsViewEvent.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,11 +9,12 @@ export const useGtmAutocompleteResultsViewEvent = (
searchQuery: string,
): void => {
const lastViewedAutocompleteResults = useRef<AutocompleteSearchQueryApi>();
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]);
};
5 changes: 4 additions & 1 deletion storefront/gtm/hooks/useGtmCartViewEvent.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 &&
Expand All @@ -25,5 +28,5 @@ export const useGtmCartViewEvent = (gtmPageViewEvent: GtmPageViewEventType): voi
),
);
}
}, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart, gtmPageViewEvent.currencyCode]);
}, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart, gtmPageViewEvent.currencyCode, didPageViewRun]);
};
6 changes: 4 additions & 2 deletions storefront/gtm/hooks/useGtmContactInformationPageViewEvent.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { useGtmContext } from 'gtm/context/useGtmContext';
import { getGtmContactInformationPageViewEvent } from 'gtm/helpers/eventFactories';
import { gtmSafePushEvent } from 'gtm/helpers/gtm';
import { GtmPageViewEventType } from 'gtm/types/events';
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]);
};
3 changes: 3 additions & 0 deletions storefront/gtm/hooks/useGtmPageViewEvent.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -8,11 +9,13 @@ export const useGtmPageViewEvent = (gtmPageViewEvent: GtmPageViewEventType, fetc
const router = useRouter();
const slug = getUrlWithoutGetParameters(router.asPath);
const lastViewedSlug = useRef<string>();
const { setDidPageViewRun } = useGtmContext();

useEffect(() => {
if (gtmPageViewEvent._isLoaded && lastViewedSlug.current !== slug && !fetching) {
lastViewedSlug.current = slug;
gtmSafePushEvent(gtmPageViewEvent);
setDidPageViewRun(true);
}
}, [gtmPageViewEvent, fetching, slug]);
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { useGtmContext } from 'gtm/context/useGtmContext';
import { getGtmPaymentAndTransportPageViewEvent } from 'gtm/helpers/eventFactories';
import { gtmSafePushEvent } from 'gtm/helpers/gtm';
import { GtmPageViewEventType } from 'gtm/types/events';
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 &&
Expand All @@ -18,5 +21,5 @@ export const useGtmPaymentAndTransportPageViewEvent = (gtmPageViewEvent: GtmPage
getGtmPaymentAndTransportPageViewEvent(gtmPageViewEvent.cart.currencyCode, gtmPageViewEvent.cart),
);
}
}, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart]);
}, [gtmPageViewEvent._isLoaded, gtmPageViewEvent.cart, didPageViewRun]);
};
6 changes: 4 additions & 2 deletions storefront/gtm/hooks/useGtmProductDetailViewEvent.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -11,11 +12,12 @@ export const useGtmProductDetailViewEvent = (
): void => {
const lastViewedProductDetailSlug = useRef<string | undefined>(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]);
};

0 comments on commit 800d598

Please sign in to comment.