Using Redux to dispatch your GTM events

Often when building sites, implementing Google Analytics is one of the requirements in your definition of done. Implementing all of those tags can lead to a spaghetti of code triggering events all over the place, but there is a way of keeping it nice and clean. In this case we’re pushing the events to Google Tag Manager which from there can be captured by Google Analytics. Together with the usage of React and Redux in your project, adding an analytics middleware could be the best solution. Inside the middleware you will have a central place of dispatching where the specific analytics actions will be handled correctly.

Let’s start with a very basic use case of tracking if a user has clicked on a button. First create a basic React component for our button.

import React from 'react';
import PropTypes from 'prop-types';
const Button = ({ type, button }) => <button type={type}>{value}</button>;
Button.propTypes = {
type: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
export default Button;

After this we can start with building the Redux ‘magic’. Let’s start with creating an action . We start off with an analytics-actions file with the following contents.

import { createAction } from 'redux-actions';
import nskeymirror from 'nskeymirror';
const actions = nskeymirror({
buttonClick: null
}, 'analytics');
export const buttonClick = createAction(actions.buttonClick);

We’ve created a buttonClick action and i’ve used nskeymirror so we can easily map the Redux action to the correct namespace buttonClick@analtyics.

Next step would be to pass the created action into the button component. Create a ButtonContainer file or add the action in a higher order connect component (then you can pass the buttonClick property down to the button component).

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { buttonClick } from './analytics-actions';
import Button from './Button';
const mapDispatchToProps = (dispatch) => bindActionCreators({
}, dispatch);
export default connect(null, mapDispatchToProps)(Button);

We’ve now passed the action into the properties of the button component with mapDispatchToProps. Mind that the first parameter in the connect function stands for mapStateToProps, in this case we can leave it to null as we don’t want to pass state properties at this moment.

With the action now available in the properties, we can expand our button component to receive the buttonClick property and actually do something with it. With the addition of the handleGtmEvent function we have to rewrite the component from arrow function to class notation. In the buttonClick action we pass the value of the button.

class Button extends React.Component {
constructor(props) {

this.handleGtmEvent = () => {
const { buttonClick, value } = this.props;
buttonClick({ value });
  render() {
const { type, value } = this.props;
    return (
<button onClick={this.handleGtmEvent} type={type}>
export default Button;

After creating the action and the updating the component we can begin with creating our middleware. In this case we only need the next and action properties (first property would be the store).

From the action property we can destructure the properties type and payload which we are going the need. The type is the action being called and in the payload we can find the value of the button that we’ve passed.

There are a few tricky things here to look at. To match the type with the action you’ll need to cast the action to a string by by adding the .toString() function and also don’t forget to return next with action. Otherwise the passing of your actions will stop here which causes strange behaviour in your store.

import { buttonClick } from './analytics-actions';
import eventTrack from './event-track';
const analyticsMiddleware = () => next => (action) => {
const { payload, type } = action;
  switch(type) {
case buttonClick.toString(): {
eventTrack('buttonClick', payload);


return next(action);
export default analyticsMiddleware;

Inside the switch we can create the event and send it to the data layer. Let’s create a simple event-track file where we can easily push our events.

const sendEvents = ( => {
const dataLayer = window.dataLayer || [];
window.dataLayer = dataLayer;
export default (event, eventData) => {
switch(event) {
case 'buttonClick':
sendEvents({ event: 'buttonClick', value: eventData.value });

throw new Error(`Unrecognized GTM event: ${event}`);

Final step is adding the created middleware to the creation your Redux store and you can start dispatching the events and filling the data layer!

Thank you for taking the time to reading this story! If you enjoyed reading this story, click the 👏🏻 below so other people will see this here on Medium.

Also, I work at one of the biggest e-commerce companies of the Netherlands. We do have a tech blog, check it out and subscribe if you want to read more stories like this one. Or look at our job offers if you are looking for a great job!