Implementing Google Analytics to React with TypeScript

Önder Alkan
6 min readNov 13, 2023

--

Photo by Justin Morgan on Unsplash

Sometimes we need to collect some user data to give some meaning to the usage of our website. We may want to track page views or custom events like if a button is clicked or a page is scrolled to a certain point. To solve this issue, we have applications such as Google Analytics, Azure Application Insights, HotJar and some more. Each of them has their own pros and cons.

If you want to have a more “user” friendly dashboard and any non-technical people to be able to track data on your website, the first analytics app comes to mind is Google Analytics. It’s not like developer intensive or technical ability required, like Azure Application Insights. And it’s not like super resource and data intensive like HotJar. So, it’s basically a general mainstream telemetry collection app.

Photo by Pablo Arroyo on Unsplash

This article will be only about React, not any other JSX based framework or libraries.

Installing necessary packages

Google Analytics has its own SDK to use in non SPAs, but to have a clean and readability code we will use “react-ga4” package. And for creating unique identifiers for each client, we will use “uuid” package.

npm i react-ga4 uuid
npm i -D @types/uuid

After the installation is done, now we can create the initialization file where we define how our data should be collected.

Create an initialization file

For Google to be able to start collecting data from our website, first we need to tell it what to do (duh).

We will start by creating a file called “ga-init.ts”.

import ReactGA from ‘react-ga4';
import generateClientIdGa from ‘./generate-client-id-ga’;

export const gaEnvKey = ‘REACT_APP_GOOGLE_ANALYTICS_TRACKING_ID’;

const ga = {
initGoogleAnalytics() {
const trackingId = process.env[gaEnvKey];
if (!trackingId) console.warn("No tracking id is found for Google Analytics")

ReactGA.initialize([
{
trackingId,
gaOptions: {
anonymizeIp: true,
clientId: generateClientIdGa()
}
}
]);
}
};

export default ga;

Key point:

You need an environment variable called “REACT_APP_GOOGLE_ANALYTICS_TRACKING_ID”

otherwise, it won’t work.

We are using an object here to be able to create functions to support multiple functionalities such as page views or tracking events, even maybe setting some configuration on post initialization.

Yes ReactGA is doing the exact same thing then why we create such object?

Because in future we may not want to use ReactGA maybe because it won’t be maintained anymore or Google will release Google Analytics 5 and this package will have hard time to implement.

By having such object, we won’t have to edit each instance of ReactGA in the code god knows how many we’ll have. We will only modify this object and nothing else in the code, so we’ll make sure that we have single point of functionality of collecting data.

Every key-value you will provide to the gaOptions object, will be modify the Google Analytics and how it will collect the data. For the all options available, you can visit here: Analytics.js Field Reference | Analytics for Web (analytics.js) | Google for Developers

For this example, I want to anonymized the IP as well, so we won’t record any IPs of the clients.

clientId: a unique identifier for each browser. We will save this unique identifier in the local storage and will use it as long as it exists in the local storage, otherwise we’ll create a new one and set that value to the local storage.

Let’s define the generateClientIdGa function. Create a file called “generate-client-id-ga.ts”.

import { v4 as uuidv4 } from 'uuid';

export const gaClientStorageKey = 'ga-gtag-client-id';

const generateClientIdGa = () => {
let clientId = localStorage.getItem(gaClientStorageKey);

if (!clientId) {
clientId = uuidv4();
localStorage.setItem(gaClientStorageKey, clientId);
}

return clientId;
};

export default generateClientIdGa;

This file will get or create a unique identifier for clientId.

Create load analytics file

Now we will create a file that will include only the initialization of the Google Analytics. It will be look like this (name it as load-analytics.ts)

import ga from './ga-init';

const loadAnalytics = () => {
ga.initGoogleAnalytics();
};

export default loadAnalytics;

Okay, what the hell was that right? It’s just a single execution of a function, why we need a wrapper around it?

The thing is, do we know that there won’t be any other analytics application requirement for your app? In most cases, no. So, in here we simply hold all analytics app initializations together, even if it’s one (sometimes you cannot foresee future).

And also, you probably want things clear and tidy in your code(!)

Initialize the Google Analytics

This is the most critical part of implementing the Google Analytics, because without initializing the Google Analytics on the React application, it means there will be absolutely no transfer of data to Google.

We all know this file; it comes as default when you create a React project with CRA.

import './index.css';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import loadAnalytics from './load-analytics';

const element: HTMLElement | null = document.getElementById('root');
const root = ReactDOM.createRoot(element as Element);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

loadAnalytics();

As you can see, we imported the loadAnalytics function and executed right after the ReactDOM creates an app.

Couldn’t we do this with React way? Yes, we could. We could’ve created a component which passes children only but executed loadAnalytics in the component body. All we had to do is wrapping App component with it then. Like this below:

GAInitializer.tsx

import ga from './ga-init';

const GAInitializer = () => {
ga.initGoogleAnalytics();

return <>{children}</>
}

export default GAInitializer;

index.tsx

import './index.css';
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import GAInitializer from './GAInitializer';

const element: HTMLElement | null = document.getElementById('root');
const root = ReactDOM.createRoot(element as Element);
root.render(
<React.StrictMode>
<GAInitializer>
<App />
</GAInitializer>
</React.StrictMode>
);

But I prefer calling it out of React.

This is all you need to do for getting Google Analytics to work. You can stop reading here if you got what you need. Everything below is just fantasy.

Remember you can always grow “ga-init.ts” file and “ga” object. Let’s add some methods to the “ga” object to use. For example let’s add userId to the Google Analytics to know which data belongs to which user.

Let’s add setOption method to the “ga” object as below:

setOption(key: string, value: unknown) {
ReactGA.set({ [key]: value });
}

And add another method called setUserId to the “ga” object:

setUserId(userId: string | number) {
this.setOption("userId", userId);
}

Now you can use this method in your authorization methods to set a userId or user email so Google Analytics will know that which data belongs to which user.

Another useful method would be sending the page view data. But as always, we’ll first write how to send anything to Google:

enum HitTypes {
PageView = "pageview"
}

sendData(type: HitTypes, data: Object) {
ReactGA.send({hitType: type, ...data})
}

And now actual page tracking method:

enum HitTypes {
PageView = "pageview"
}

trackPageView(pageTitle?: string, pagePath?: string) {

if (!pageTitle) {
pageTitle = document.title;
}

if (!pagePath) {
pagePath = location.href
}

this.sendData(HitTypes.PageView, { page: pagePath, title: pageTitle });
}

Call it on your useEffect of your page components and page view information will be collected.

And finally let’s add a custom event tracker,

Let’s do this a bit different and use closure here and go with a builder:

type EventTrackOptions = Omit<UaEventOptions, 'category'>

trackEventBuilder(categoryName: string) {
return (options: EventTrackOptions) => {
ReactGA.event({ category: categoryName, ...options });
};
}

We can now use it like this:

const trackVideoEvent = ga.trackEventBuilder("Video")

trackVideoEvent({action: "videoStart", label: "Video started", value: 0})
trackVideoEvent({action: "videoWatching", label: "Video watching", value: 10})
trackVideoEvent({action: "videoEnded", label: "Video ended", value: 100})

That’s all I can share about this topic.

This would be the latest version of “ga” object:

import ReactGA from 'react-ga4';
import generateClientIdGa from './generate-client-id-ga';

export const gaEnvKey = 'REACT_APP_GOOGLE_ANALYTICS_TRACKING_ID';

const ga = {
initGoogleAnalytics() {
const trackingId = process.env[gaEnvKey];
if (!trackingId) console.warn("No tracking id is found for Google Analytics")

ReactGA.initialize([
{
trackingId,
gaOptions: {
anonymizeIp: true,
clientId: generateClientIdGa()
}
}
]);
},
setOption(key: string, value: unknown) {
ReactGA.set({ [key]: value });
},
setUserId(userId: string | number) {
this.setOption('userId', userId);
},
sendData(type: HitTypes, data: Object) {
ReactGA.send({ hitType: type, ...data });
},
trackPageView(pageTitle?: string, pagePath?: string) {
if (!pageTitle) {
pageTitle = document.title;
}

if (!pagePath) {
pagePath = location.href;
}

this.sendData(HitTypes.PageView, { page: pagePath, title: pageTitle });
},
trackEventBuilder(categoryName: string) {
return (options: Omit<UaEventOptions, 'category'>) => {
ReactGA.event({ category: categoryName, ...options });
};
}
};

Enjoy!

--

--