Mark Khuzam

Mark Khuzam

Dec 15, 2020

Setting Up Google Analytics with React Context in Next.js

Setting Up Google Analytics with React Context in Next.js

Google Analytics is a free tool offered by Google to assist you in analyzing web traffic. The topic of Analytics and User Tracking is a pretty deep dive and out of the scope of this tutorial, if you'd like to learn more about Google Analytics, this article offers a good starting point for what Google Analytics is and why it's important to your business.

If you're completely new to GA, you'll want to visit the application and set up an account. (Gmail required)

After you're done signing up, you'll be provided with a Tracking ID for your website.

It should look something like

UA-XXXXXXXX-X

React + Google Analytics

There are many great tutorials about setting up GA in a React application, and most will get you to where you need. Where this tutorial differs is the use of React Context.

By utilizing a Context, we can keep a React State object that holds information to handle multiple use cases, such as, a unique User ID, multiple GA Tracking ID's, user provided Tracking ID's, and more.

To handle communicating with GA we'll use a popular utility library, react-ga

Installing the library can be done by using the command

yarn add react-ga

Creating the Context

We'll start by creating a Tracking Context which will be used to provide our App with the an API for logging events as well as initializing GA when the app loads.

// contexts/trackers.jsx
import React from 'react';

const TrackingID = 'UA-XXXXXXXX-X';

const TrackingContext = React.createContext();

function TrackingProvider(props) {
    return <TrackingContext.Provider {...props} />;
}

const useTracking = () => React.useContext(TrackingContext);

export { TrackingProvider, useTracking };

Up to this point, what we've done is create a Context that we can use to wrap Components that will utilize tracking. Most applications will benefit from wrapping the entire application with a Tracking Context but use cases will vary so I recommend applying it where you think is best for your application.

Integrating into Next.js

Next.js is a wonderful React framework that offers a quick way to implement SSR (server-side rendering). If you've never used Next before, I recommend following their Create a Next.js App tutorial.

To provide tracking to all of our components, we'll need to use the Tracking Provider in a custom next app file.

// pages/_app.js

import { TrackingProvider } from './contexts/tracking';

function App({ Component, pageProps }) {
    return (
        <AllYourOtherProviders>
            <TrackingProvider>
                <Component {...pageProps} />
            </TrackingProvider>
        </AllYourOtherProviders>
    );
}

Now that the rest of our App has access to our TrackingProvider, we can start to initialize Google Analytics and track all page views that occur within the app.

// contexts/trackers.jsx

import React, { useState, useEffect } from 'react';
import Router from 'next/router';

const TrackingID = 'UA-XXXXXXXX-X';
const TrackingContext = React.createContext();

function TrackingProvider(props) {
    // if the userId is passed in, we'll need to keep track of any
    // login/logout changes
    // another method is presented below, this will vary depending
    // on your authentication method
    const { userIdThatMightChange } = props;

    // we create a default state to keep track of whether GA
    // has been initialized, if we're tracking a unique user,
    // and to hold all of our trackers

    const [analytics, setAnalytics] = useState({
        isInitialized: false,
        hasUser: false,
        trackers: ['myDefaultTracker']
    });

    // We create a function handle all route changes that occur
    // and track a users movements across pages in our app

    const handleRouteChange = (url) => {
        ReactGA.set({ page: url }, analytics.trackers);
        ReactGA.pageview(url, analytics.trackers);
    };

    // We only want to initialize GA on the client side
    // This will fail if you're trying to initialize server side
    // useEffect will help us handle this case as it only runs
    // client side

    useEffect(() => {
        const { isInitialized, hasUser, trackers } = analytics;

        // How you detect which user is currently logged in
        // depends on the way you've set up authentication within
        // your app, the important thing is getting the userId

        const userId = getUserFromApi();

        // initialize GA with our tracking id
        // uncomment the user tracking method that works for you

        if (!isInitialized) {
            ReactGA.initialize(TrackingID, {
                ...variousOptions,
                gaOptions: {
                    userId: userId
                    // userId: userIdThatMightChange
                }
            });

            // Handle all route changes

            Router.events.on('routeChangeComplete', handleRouteChange);

            setAnalytics((prev) => ({
                ...prev,
                isInitialized: true,
                hasUser: Boolean(userId)
            }));

            // in case we dont have the user initially,
            // we handle setting a user in our tracker
        } else if (isInitialized && !hasUser) {
            ReactGA.set({ userId }, trackers);

            setAnalytics((prev) => ({
                ...prev,
                hasUser: Boolean(userId)
            }));
        }

        return () => {
            // clean up
            Router.events.off('routeChangeComplete', handleRouteChange);
        };
    }, [userIdThatMightChange]);

    return <TrackingContext.Provider {...props} />;
}

const useTracking = () => React.useContext(TrackingContext);

export { TrackingProvider, useTracking };

If you've made it this far, great!

So far we've:

  1. Initialized Google Analytics
  2. Are tracking unique users in our App
  3. Are tracking all page changes within our app

Now what remains is:

  • [ ] logging specific events that occur within the app
  • [ ] adding multiple trackers to GA.

Adding Multiple Trackers

Advanced analytics will require multiple trackers wether for users or for your own business. Extending the GA trackers requires another trackingId as well as naming each of trackers.

    // contexts/trackers.js

    function TrackingProvider(props) {
        ...
        // We'll define our addTracker function before useEffect
        const addTracker = (trackerId, trackerName) => {
          if (analytics.isInitialized) {
            ReactGA.addTrackers([
                {
                    trackingId:  trackerId,
                    gaOptions: {
                        name:  trackerName
                    }
                }
            ]);
            setAnalytics((prev) => ({
                ...prev,
                trackers: [...prev.trackers, trackerName]
            }))
          }
        }
        const removeTracker = (trackerName) => {
            if (analytics.isInitialized) {
            setAnalytics((prev) => ({
                ...prev,
                trackers: prev.trackers.filter((tracker) => tracker !== trackerName)
            }))
            }
        }
        useEffect(() => {
            ...
        })
        ...
        return <TrackingContext.Provider
            value={{ addTracker, removeTracker }}
            {...props}
            />
    }

Logging Events in our Application

We'll extend our TrackingContext with a logEvent method. This will allow us to have access to our tracking utility while keeping track of whether GA is initialized and the current user accessing it.

    // contexts/trackers.js
    function TrackingProvider(props) {
        ...
        // We'll define our logEvent function before useEffect
        const logEvent = ({ category = '', action = '', label = '' }) => {
          if (analytics.isInitialized) {
              ReactGA.event({
              category,
              action,
              label
              }, analytics.trackers)
          }
        }
        useEffect(() => {
        ...
        })
        ...
        return <TrackingContext.Provider
            value={{ logEvent, addTracker, removeTracker }}
            {...props}
            />
    }

ReactGA offers more information on the type of data you can send to Google Analytics. I recommend reviewing their documentation to extend it to your use case.

Using logEvent in your components

To use the tracker, we'll import our useTracking method into the components where specific events occur. This example component submits a form (use your imagination for the rest of the code)

// components/form.jsx

import { useTracking } from 'contexts/tracker';

function Form(props) {
    const { logEvent } = useTracking();
    return (
        <form
            onSubmit={() => {
                // code that does something to submit data
                logEvent({
                    category: 'ExampleCategory',
                    action: 'Submitted Data',
                    label: 'Special Label'
                });
            }}>
            <button type="submit">Submit</button>
        </form>
    );
}

Thanks for following along!

This setup is a little more than what's required but extending tracking to use a unique user and localizing event logging greatly benefits the scalability of your application and decreases logging complexity.

Extend the tracking context even more by integrating a Facebook Pixel and other tracking utilities like Segment and MixPanel!