All files / react-app/src/utils/ReactGA PathTracker.tsx

100% Statements 35/35
75% Branches 6/8
100% Functions 8/8
100% Lines 35/35

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102                                  5x     5x   5x   5x 7x               5x 7x 7x 7x 7x 7x                 5x     5x 3x 3x     3x 1x       2x     2x     2x 2x       5x 5x   5x 2x 2x   5x 1x 1x       5x     5x   5x     5x 5x 5x              
import { useEffect, useRef } from "react";
 
import { sendGAEvent } from "./SendGAEvent";
 
type PathTrackerProps = {
  userRole: "cms" | "state";
  children: React.ReactNode;
};
 
/**
 * Wrap around your <C.Layout>.
 * It will send:
 *   1) a `custom_page_view` event on initial mount or after every route change
 *   2) a `page_duration` event when the user leaves the previous route
 */
export default function PathTracker({ userRole, children }: PathTrackerProps) {
  // keep track of the path of the page the user is leaving
  const prevPathRef = useRef<string>(window.location.pathname);
 
  // record when the current route was "entered"
  const startTimeRef = useRef<number>(Date.now());
 
  useEffect(() => {
    //for tracking page views
    const sendPageView = (path: string) => {
      sendGAEvent("page_view", {
        page_path: path,
        referrer: prevPathRef.current || "",
        ...(userRole && { user_role: userRole }),
      });
    };
 
    //for tracking duration spent on page
    const sendPageDuration = (path: string, startTs: number) => {
      Eif (userRole) {
        const now = Date.now();
        const deltaMs = now - startTs;
        const timeOnPageSec = Math.round(deltaMs / 1000); // nearest second
        sendGAEvent("page_duration", {
          page_path: path,
          user_role: userRole,
          time_on_page_sec: timeOnPageSec,
        });
      }
    };
 
    // send page_view for the first load
    sendPageView(window.location.pathname);
 
    // when a route change is detected
    const onRouteChange = () => {
      const newPath = window.location.pathname;
      const oldPath = prevPathRef.current;
 
      // if the path didn’t actually change, do nothing
      if (newPath === oldPath) {
        return;
      }
 
      // 1) send page_duration for the old path
      sendPageDuration(oldPath, startTimeRef.current);
 
      // 2) send a new page_view for the new path, with referrer = old path
      sendPageView(newPath);
 
      // 3) update prevPath and reset startTime for the new page
      prevPathRef.current = newPath;
      startTimeRef.current = Date.now();
    };
 
    //pushState/replaceState so we catch in‐app ract navigation
    const origPush = window.history.pushState;
    const origReplace = window.history.replaceState;
 
    window.history.pushState = function (this: History, ...args: any[]) {
      origPush.apply(this, args);
      onRouteChange();
    };
    window.history.replaceState = function (this: History, ...args: any[]) {
      origReplace.apply(this, args);
      onRouteChange();
    };
 
    //Also catch Back/Forward browser buttons
    window.addEventListener("popstate", onRouteChange);
 
    //cleanup- when PathTracker unmounts
    return () => {
      //send duration for whichever page user was on
      sendPageDuration(prevPathRef.current, startTimeRef.current);
 
      //restore history methods and remove listener
      window.history.pushState = origPush;
      window.history.replaceState = origReplace;
      window.removeEventListener("popstate", onRouteChange);
    };
  }, [userRole]);
 
  // Render children as usual
  return <>{children}</>;
}