How to Add Google Analytics to Redux

Ben Shope
3 min readJul 22, 2018

--

Recently I was tasked with adding Google Analytics to a React/Redux app for tracking an open-source project that my team at work built. While looking into it, I realized that integrating Redux and Google Analytics could be a confusing topic.

Why it’s complex

  1. There are a ton of packages on npm that claim to add analytics to a Redux app. These packages are at best unnecessary — at worst they will make this task more complicated than it needs to be.
  2. Google Analytics may not be loaded the same way as your other scripts (included via npm). This is a package that you want to show up even if your bundle fails, so in many instances it *might* be worthwhile to load it by including the standard HTML script tag that Google Analytics provides.

The requirements

  • control over the actions that are tracked
  • control over the payloads that are being sent with those actions
  • separation of concerns — where the files containing your existing actions and middleware do not have random tracking code in them

A solution

There are two good ways to add analytics to a Redux app. The first involves making analytics calls from your existing async middleware (redux-saga, redux-observable, etc.) and the second involves writing a custom middleware. The differences between these solutions are somewhat cosmetic - meaning the choice depends on the question “how much do you enjoy leaning on your current async middleware?”

Although I prefer using redux-saga or redux-observable, for the purpose of this article I will touch on writing custom middleware. Sounds hard? Well, it isn’t. A middleware is just one single function that looks like this:

const analyticsMiddleware = store => next => action => {
if (window.gtag) {
window.gtag('event', 'action', {
event_category: action.type,
event_label: JSON.stringify(action.payload || {})
});
}
return next(action);
};
export default analyticsMiddleware;

Here is a slightly more complicated form of the same function that gives you complete control over the payloads that you’re sending to Google Analytics:

// import your own action names
const SampleActions = {
UPDATE_USER: 'UPDATE_USER'
POST_A_MESSAGE: 'POST_A_MESSAGE'
SCROLL_DOWN: 'SCROLL_DOWN',
HOVER_MENU: 'HOVER_MENU'
};
const trackingInformation = {
// gets tracking information just by filtering the action
[SampleActions.POST_A_MESSAGE]: ({id, content}) => ({id, length: content.length}),
// gets tracking information from the action and the store
[SampleActions.UPDATE_USER]: ({id, enabled}, store) => ({
id, name, friends: store.getState().friends
})
};
// do not track these
const EXCLUDED_ACTIONS = [SampleActions.SCROLL_DOWN, SampleActions.HOVER_MENU];
const analyticsMiddleware = store => next => action => {
if (window.gtag && !EXCLUDED_ACTIONS.includes(action.type)) {
// eslint-disable-next-line no-undef
window.gtag('event', 'action', {
event_category: action.type,
// defaults to tracking the entire stringified payload
event_label: JSON.stringify(trackingInformation[action.type]
? trackingInformation[action.type](action.payload, store)
: action.payload || {})
});
}
return next(action);
};
export default analyticsMiddleware;

and then all you need to do is to add this middleware to your existing stack by doing something like this:

import analyticsMiddleware from './path-to-analytics-middleware';...export const middlewares = [
thunk,
routerMiddleware(hashHistory),
analyticsMiddleware];
export default createStore(
combinedReducers,
initialState,
compose(applyMiddleware(...middlewares)));

If you want to see this bit of code implemented in a live repo, here is a link to the PR: https://github.com/uber/kepler.gl/pull/119/files#diff-d8ef93fc2b4d4f2fbf497c4a67934192

Good luck adding analytics to your own project! Please share your thoughts or comments.

[EDIT 2019]: with recent developments in terms of context, hooks, StateX, GraphQL etc., many apps are eschewing Redux altogether. Middleware can be integrated with other state and event management methodologies — meaning the core idea behind this article remains relevant. I will update this article or create a new article when I eventually add tracking to a non-Redux app

--

--

Ben Shope

Software engineer, indoor gardener, and oxford comma user