Yotpo SMS & Email Web Pixel Installation Guide for Shopify Hydrogen 2
    • Dark
      Light

    Yotpo SMS & Email Web Pixel Installation Guide for Shopify Hydrogen 2

    • Dark
      Light

    Article summary

    Products


    SMS & Email
    Supported plans

    Free, Starter, Pro, Premium, Enterprise

    eCommerce Platform

    Shopify, Shopify Plus

    Use this repository as a guideline for implementing Yotpo SMS customer behavior tracking on Shopify Hydrogen 2 storefronts that are using the Remix.run framework.

    Before you proceed, please make sure you have the Yotpo SMS & Email (SMSBump) sales channel installed on your Shopify store.

    Dependencies

    Make sure you have the @shopify/hydrogen-react library added to your dependencies. We will use it to load the JS SDK script.

    Analytics layer in Hydrogen 2

    The examples shown here are based on the recommended approach of collecting analytics, as described in this Shopify article.

    At the time of writing this guide, the usePageAnalytics() hook is not shipped out of the box when you create a hydrogen project using npm create @shopify/hydrogen.

    Make sure you add it to app/utils.js:

    import { useMatches, useMemo } from '@remix-run/react'
    
    export function usePageAnalytics() {
      const matches = useMatches();
    
      const analyticsFromMatches = useMemo(() => {
        const data = {};
    
        matches.forEach((event) => {
          const eventData = event?.data;
          if (eventData) {
            eventData['analytics'] && Object.assign(data, eventData['analytics']);
          }
        });
    
        return data;
      }, [matches]);
    
      return analyticsFromMatches;
    }
    Warning:

    Although the usePageAnalytics() hook is provided for thoroughness, it's generally a good idea to check the original guide for an updated version.

    useYotpoSms() hook and context key constants

    Create an app/analytics/yotposms.js.

    import { useLoadScript } from "@shopify/hydrogen-react";
    import { useEffect, useRef } from "react";
    
    export const YOTPOSMS_ANALYTICS_PAGEVIEW = 'yotpoSmsPageview'
    export const YOTPOSMS_ANALYTICS_EVENT = 'yotpoSmsEvent'
    
    export default function useYotpoSms(opts = {}) {
        const dataLayer = useRef([])
    
        Object.assign(opts, {
            platform: 2,
        })
    
        useLoadScript('https://d18eg7dreypte5.cloudfront.net/browse-abandonment/v2/generic.js')
    
        useEffect(() => {
            window.wtba = window.wtba || []
    
            if (Array.isArray(dataLayer.current) && dataLayer.current.length) {
                window.wtba.push(...dataLayer.current)
            }
    
            dataLayer.current = window.wtba
            dataLayer.current.push({ type: 'config', options: opts })
        }, [])
    
        return dataLayer
    }

    It provides 3 exports:

    • the useYotpoSms( options ) hook, which will load the SDK script and expose the data layer object
    • YOTPOSMS_ANALYTICS_PAGEVIEW constant 
    • YOTPOSMS_ANALYTICS_EVENT constant 

    Update routes and actions

    You need to update your routes and actions so they can start broadcasting analytics data.

    Homepage route

    import { YOTPOSMS_ANALYTICS_PAGEVIEW } from '~/analytics/yotposms';

    Update your json()/defer() within the loader() of app/routes/_index.jsx, so that it looks as follows:

      return defer({
        featuredCollection,
        recommendedProducts, 
    +   analytics: {
    +     [YOTPOSMS_ANALYTICS_PAGEVIEW]: {
    +       type: 'home',
    +     }
    +   }
      });

    Update AddToCartButton component

    If you followed Shopify’s guide on tracking add-to-cart events, then you should have the AddToCartAnalytics Higher Order Component. We want to use the useYotpoSms() hook there so we can push those events to the data layer. 

    In your app/components/AddToCartButton.jsx:

    import { useRouteLoaderData } from "@remix-run/react";
    import useYotpoSms, { YOTPOSMS_ANALYTICS_EVENT } from "~/analytics/yotposms";
    
    function AddToCartAnalytics({
        fetcher,
        children,
    }) {
        // Data from action response
        const fetcherData = fetcher.data;
        // Data in form inputs
        const formData = fetcher.formData;
    +   const rootData = useRouteLoaderData('root');
    +
    +   const yotpoDataLayerRef = useYotpoSms({
    +       store: rootData.publicStoreDomain,
    +   })
    +
        useEffect(() => {
            if (formData) {
                const cartData = {};
                const cartInputs = CartForm.getFormInput(formData);
    
                try {
                    // Get analytics data from form inputs
                    if (cartInputs.inputs.analytics) {
                        const dataInForm = JSON.parse(
                            String(cartInputs.inputs.analytics),
                        );
                        Object.assign(cartData, dataInForm);
                    }
                } catch {
                    // do nothing
                }
    
                // If we got a response from the add to cart action
                if (Object.keys(cartData).length && fetcherData) {
                    // send add to cart event
    +               cartData.products?.forEach(event => {
    +                   if (event && event[YOTPOSMS_ANALYTICS_EVENT]) {
    +                       yotpoDataLayerRef.current.push(event[YOTPOSMS_ANALYTICS_EVENT])
    +                   }
    +               })
                }
            }
        }, [fetcherData, formData]);
        return <>{children}</>;
    }

    Product route

    import { YOTPOSMS_ANALYTICS_EVENT, YOTPOSMS_ANALYTICS_PAGEVIEW } from '~/analytics/yotposms';

    Update your json()/defer() within loader() of app/routes/products.$handle.jsx, so that it looks as follows:

      return defer({
        product,
        variants,
    +   analytics: {
    +     [YOTPOSMS_ANALYTICS_PAGEVIEW]: {
    +       type: 'product',
    +       id: product.id.replace('gid://shopify/Product/', ''),
    +       variant_id: product.selectedVariant.id.replace('gid://shopify/ProductVariant/', ''),
    +     }
    +   }
      });

    In order to receive analytics on products added to cart, add the productAnalytics prop to your <AddToCartButton/> component.

     <AddToCartButton
        // existing props, 
    +   productAnalytics={{
    +       [YOTPOSMS_ANALYTICS_EVENT]: {
    +           type: 'event',
    +           event: 'product_added_to_cart',
    +           id: product.id.replace('gid://shopify/Product/', ''),
    +           variant_id: selectedVariant.id.replace('gid://shopify/ProductVariant/', ''),
    +       }
    +   }}
    >
        {selectedVariant?.availableForSale ? 'Add to cart' : 'Sold out'}
     </AddToCartButton>

    Collection route

    import { YOTPOSMS_ANALYTICS_PAGEVIEW } from '~/analytics/yotposms';

    Update yourjson()/defer() within loader() oflapp/routes/collections.$handle.jsx, so it looks as follows:

    
      return json({
        collection,
    +   analytics: {
    +     [YOTPOSMS_ANALYTICS_PAGEVIEW]: {
    +       type: 'collection',
    +       id: collection.id.replace('gid://shopify/Collection/', ''),
    +     }
    +   }
      });

    Logout route

    We will set a flash session on customer logout to be able to notify the tracking script about this event.
    Update the action() handler of your app/routes/account_.logout.jsx, setting the session just before the redirect().

    + session.flash('customerJustLoggedOut', true)
    
      return redirect('/', {
        headers: {
          'Set-Cookie': await session.commit(),
        },
      });

    Account route

    We will use the account route to "catch" the current logged-in customer. This is purely illustrative and we do it this way here because customers are redirected to the profile page upon login, and we already have the customer object there. You can achieve the same on any other route if your use case differs.

    import { YOTPOSMS_ANALYTICS_EVENT } from '~/analytics/yotposms';

    Update your loader() within app/routes/account.jsx route, so that it looks as follows:

    const { customer } = await storefront.query(CUSTOMER_QUERY, {
          variables: {
            customerAccessToken: customerAccessToken.accessToken,
            country: storefront.i18n.country,
            language: storefront.i18n.language,
          },
          cache: storefront.CacheNone(),
        });
    
        if (!customer) {
          throw new Error('Customer not found');
        }
    
    +   const analytics = {}
    +
    +   Object.assign(analytics, {
    +     [YOTPOSMS_ANALYTICS_EVENT]: (({ email, phone }) => ({
    +       type: 'customer',
    +       event: 'customer_login',
    +       customer: { phone, email },
    +     }))(customer)
    +   })
    
        return json(
    -     { isLoggedIn, isPrivateRoute, isAccountHome, customer },
    +     { isLoggedIn, isPrivateRoute, isAccountHome, customer, analytics },
          {
            headers: {
              'Cache-Control': 'no-cache, no-store, must-revalidate',
            },
          },
        );

    App Root (app/root.jsx)

    import { useLocation } from '@remix-run/react';
    import { useEffect, useMemo } from 'react';
    import useYotpoSms, { YOTPOSMS_ANALYTICS_EVENT, YOTPOSMS_ANALYTICS_PAGEVIEW } from './analytics/yotposms';
    import { usePageAnalytics } from './utils';

    In the loader(), after validating the customer access token, check for the flash logout session and pass it down to <App/> using json()/defer().

      // validate the customer access token is valid
      const {isLoggedIn, headers} = await validateCustomerAccessToken(
        customerAccessToken,
        session,
      );
    
      ...
    
    + const analytics = {}
    +
    + if (! isLoggedIn) {
    +   if (session.get('customerJustLoggedOut')) {
    +     Object.assign(analytics, {
    +       [YOTPOSMS_ANALYTICS_EVENT]: {
    +         type: 'customer',
    +         event: 'customer_logout',
    +       }
    +     })
    +
    +     headers.append('Set-Cookie', await session.commit())
    +   }
    + }
    
      return defer(
        {
          cart: cartPromise,
          footer: footerPromise,
          header: await headerPromise,
          isLoggedIn,
          publicStoreDomain,
    +     analytics,
        },
        {headers},
      );

    Lastly, update your<App /> component:

    export default function App() {
      const data = useLoaderData();
    
    + const yotpoDataLayerRef = useYotpoSms({
    +   store: data.publicStoreDomain,
    + })
    +
      const location = useLocation();
      const lastLocationKey = useRef('');
      const pageAnalytics = usePageAnalytics();
    
      useEffect(() => {
        // Filter out useEffect running twice
        if (lastLocationKey.current === location.key) return;
     
        lastLocationKey.current = location.key;
    +
    +   if (pageAnalytics[YOTPOSMS_ANALYTICS_PAGEVIEW]) {
    +     yotpoDataLayerRef.current.push(pageAnalytics[YOTPOSMS_ANALYTICS_PAGEVIEW])
    +   }
    +
    +   if (pageAnalytics[YOTPOSMS_ANALYTICS_EVENT]) {
    +     yotpoDataLayerRef.current.push(pageAnalytics[YOTPOSMS_ANALYTICS_EVENT])
    +   }
      }, [location, pageAnalytics]);
    
      return (
        <html lang="en">
        ...
        </html>
      )
    }
    

    Was this article helpful?