Analytics Cookies: Google and Hotjar tags (React)

Chronicles of Chris
6 min readNov 28, 2020

The Stats


  • Before: Good
  • During: Fine
  • After: Great


  • Estimated: 1 day (8 work hours)
  • Actual: ~5 hours

Imposter Syndrome Check: Positive

UPDATE: I have found a much smoother and more successful way to do this. I will write an update article soon, but for now you can find the summary of my solution in this Stack Overflow answer:

The Background

I’m launching a website as part of the small business I have started with a buddy of mine. I’ve spent the last ~8 weeks building it in my off time and, though it’s a humble one-pager, I’m proud of what I’d made.

Something that was looming over me the whole time, though, was cookie consent.

As devs, we don’t often pay much mind to bureaucratic bs or legal minefields. In a corporation, that stuff will be handled by people with more knowledge than you. you might have to follow orders implement something, but if legal/HR/finance didn’t check their facts, it won’t be your head that rolls.

As self-starters, that ain’t so. In my company, the buck stops with me, so I gotta make sure my cookies are being dropped only to the right users and only at the right time.

The Feeling

Accomplished, professional, and quite smart. I feel in control. Which is slightly sociopathic, granted, but how often do we feel utterly at the mercy of our tech, our copied-in code, our npm packages? I feel like I have agency over my code, my business, and my development. It feels great.

The Solution

I completed this problem in two phases:

  1. Adding a cookie consent banner to my page using an npm package.
  2. Triggering the cookie drop based on triggers from the tags the services provide.

Let’s start at the beginning.

react-cookie-consent is an npm package that displays a consent banner on your page and can receive a series of props that can be used for customisation and user experience purposes. Here’s how I configured mine:

// imports a ready-made React component to serve as your cookie banner
import CookieConsent from "react-cookie-consent";
// implementation & configuration of cookie consent banner
style={{ alignItems: "center" }}
onAccept={() => [this.setTrackingCookies()]}
onDecline={() => {this.showModal("cookieModalShow");}}
buttonStyle={{ backgroundColor: "#009785", color: "white" }}
declineButtonStyle={{ backgroundColor: "#FFC749", color: "#000000" }}
This website uses cookies to enhance user experience. Cookies will be used for analytics, personalised content, and third-party

In order, here’s what each prop is doing:

  1. style: CSS styling of the banner.
  2. enableDeclineButton: In many countries (esp. those adhering to GDPR) you must provide users the option to reject cookies.
  3. onAccept: A fn to trigger on clicking [accept] (explained further down).
  4. onDecline: A fn to trigger on clicking [reject] (explained further down).
  5. buttonText: The text on the [accept] button.
  6. buttonStyle: CSS styling of the [accept] button.
  7. declineButtonText: The text on the [reject] button.
  8. declineButtonStyle: CSS styling of the [reject] button.
  9. overlay: Dim the background while the banner is in view.

Here’s the result:

That’s the easy part, now comes the actual, y’know, JavaScript.


Hotjar and Google Analytics both recommend inserting their script tags into the index.html of your index.html. This will automatically drop cookies on your site, regardless of whether or not the user has given permission and is a serious breach of GDPR (but what do they care? Not their asses dragged to court.) The tricky part for me was extracting the functionality I needed from their global tags and making sure they’re executed after the user has clicked accept.

onAccept, react-cookie-consent will automatically set two cookies on the user’s browser: CookieConsent: true and CookieConsent-legacy: true. This will be useful in future if I want to run a cookie check before dropping more on the user. My onAccept fn also drops the HJ and GA tags, as I’d extracted them from the script tags, but also makes use of very handy packages called react-ga and js-cookie. Here’s the JS:

import ReactGA from "react-ga";
import Cookies from "js-cookie";
setTrackingCookies = () => {
Cookies.set("CookieConsent", "true");
Cookies.set("CookieConsent-legacy", "true");
ReactGA.pageview(window.location.pathname +;
hotjarTracking = () => {
// copied-and-pasted directly from HJ. I could probably have refactored it a little to match my coding style but it's an IIFE and I don't wanna mess with that noise.
(function (h, o, t, j, a, r) {
h.hj =
h.hj ||
function () {
(h.hj.q = h.hj.q || []).push(arguments);
h._hjSettings = { hjid: {MY_HJ_ID}, hjsv: 6 };
a = o.getElementsByTagName("head")[0];
r = o.createElement("script");
r.async = 1;
r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
})(window, document, "", ".js?sv=");

Here’s what’s going on:

  • On accept, setTrackingCookies is called.
  • The cookie consent cookies are forced in case they weren’t dropped automatically ( — more on that in a sec).
  • GA is initialised by ReactGA, tags are triggered, and GA cookies are set on all pages.
  • The HJ fn is triggered — I separated it out just for the sake of cleanliness.

And as you can see, it worked!

Cookies are empty before Accept
Cookies dropped!


onDecline is a little more sophisticated, but not quite as sophisticated as I wanted it to be. GDPR states that users should be able to toggle individual cookies (or types of cookies), which requires a slightly more sophisticated system than the simple banner. For a project of this scope, I decided that I may as well switch off all cookies onDecline, rather than increasing scope by giving nuanced cookie permissions.

There was still a little hackery to do, though, as we definitely prefer users accept cookies. We can’t force them to accept cookies, but we can force them off the site if they don’t.

onDecline opens a modal explaining that the user cannot use the site without accepting the cookies. They’re given an option to accept which triggers the onAccept fn, or be redirected back to the client’s website. This explains why I needed to force-set the react-cookie-consent cookies: clicking [accept] auto-sets the react-cookie-consent cookies, but if the user first clicks [reject] on the main banner and then [accept] on the modal, the react-cookie-consent cookies wont auto-set.

Here’s the code for the decline fn:

// App.jsstate = {
cookieModalShow: false,
// fn triggered onDecline
showModal = (modal) => {
[modal]: true,
hideModal = (modal) => {
[modal]: false,
// conditional rendering of cookie modal (also toggles a "modal-overlay" class on the body of the website
{this.state.cookieModalShow ? (
hideModal={() => this.hideModal("cookieModalShow")}
) : null}

…and then…

// CookieModal.jsimport React, { Component } from "react";
import "../../App.css";
export default class CookieModal extends Component {
onClose = () => {
render() {
return (
<div className="modal">
Unfortunately, users who do not accept cookies are unable to use this website.
<div className="btn-container">
<a href="">
<button className="update">Back to Home</button>
<button className="submit" onClick={this.onClose}>

Let’s break down what’s going on here:


  • Whether or not the cookie modal is shown is stored in state. This later also controls the class “modal-overlay” for the container div of the whole page body.
  • showModal & hideModal toggle this state.
  • When cookieModalShow = true, the Cookie Modal component is rendered. Passed into the modal are two fns: our hideModal toggle and the setTrackingCookies fn.


  • The modal displays the warning to the user and gives them two options.
  • Clicking [accept] allows the user to trigger both of the props fns.
  • Clicking [Back to Home] takes the user back to the home page.

Success! One added draw back to this is: the cookie consent check only happens once, when the user first visits. So they can use the site and circumvent cookies by rejecting and then going into the site one more time. The solution for that is pending!