├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package-lock.json ├── package.json └── src ├── Snippets.js ├── TagManager.js ├── __tests__ ├── Snippets.spec.js ├── TagManager.spec.js └── warn.spec.js ├── index.js └── utils └── warn.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015" 5 | ] 6 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | __tests__ 3 | coverage -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "eslint:recommended", 4 | "env": { 5 | "es6": true, 6 | "node": true, 7 | "browser": true, 8 | "jest": true 9 | }, 10 | "parserOptions": { 11 | "ecmaFeatures": { 12 | "jsx": true 13 | } 14 | }, 15 | "rules": { 16 | "consistent-this": 0, 17 | "no-console": 0 18 | } 19 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | coverage 4 | .DS_Store 5 | dist -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6.11.0' -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 HolidayCheck 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/alinemorelli/react-gtm/master.svg?style=flat-square)](https://travis-ci.org/alinemorelli/react-gtm) 2 | [![npm version](https://img.shields.io/npm/v/react-gtm-module.svg?style=flat-square)](https://www.npmjs.com/package/react-gtm-module) 3 | [![npm downloads](https://img.shields.io/npm/dm/react-gtm-module.svg?style=flat-square)](https://www.npmjs.com/package/react-gtm-module) 4 | 5 | # react-gtm-module 6 | ### React Google Tag Manager Module 7 | 8 | This is a Javascript module to [React](https://facebook.github.io/react/) based apps that implement Google Tag Manager. It is designed to use [GTM](https://developers.google.com/tag-manager/quickstart) snippet. 9 | 10 | You can easily use custom dataLayer, multiple dataLayers and additional events. 11 | 12 | ## Installation 13 | 14 | [npm](https://www.npmjs.com/): 15 | 16 | ```bash 17 | npm install react-gtm-module --save 18 | ``` 19 | 20 | ## Usage 21 | 22 | Initializing GTM Module: 23 | 24 | ```js 25 | import React from 'react' 26 | import ReactDOM from 'react-dom' 27 | import Router from 'react-router' 28 | import routes from './routes' 29 | 30 | ... 31 | import TagManager from 'react-gtm-module' 32 | 33 | const tagManagerArgs = { 34 | gtmId: 'GTM-000000' 35 | } 36 | 37 | TagManager.initialize(tagManagerArgs) 38 | ... 39 | 40 | const app = document.getElementById('app') 41 | ReactDOM.render(, app) 42 | 43 | ``` 44 | 45 | ## DataLayer 46 | 47 | ### Custom dataLayer example: 48 | 49 | ```js 50 | import React from 'react' 51 | import ReactDOM from 'react-dom' 52 | import Router from 'react-router' 53 | import routes from './routes' 54 | 55 | ... 56 | import TagManager from 'react-gtm-module' 57 | 58 | const tagManagerArgs = { 59 | gtmId: 'GTM-000000', 60 | dataLayer: { 61 | userId: '001', 62 | userProject: 'project' 63 | } 64 | } 65 | 66 | TagManager.initialize(tagManagerArgs) 67 | ... 68 | 69 | const app = document.getElementById('app') 70 | ReactDOM.render(, app) 71 | 72 | ``` 73 | 74 | 75 | ### Multiple dataLayer example: 76 | 77 | If you need send multiple custom dataLayer you can initialize GTM Module on different components sending different dataLayers 78 | 79 | You can initialize it normally: 80 | 81 | ```js 82 | import React from 'react' 83 | import ReactDOM from 'react-dom' 84 | import Router from 'react-router' 85 | import routes from './routes' 86 | 87 | ... 88 | import TagManager from 'react-gtm-module' 89 | 90 | const tagManagerArgs = { 91 | gtmId: 'GTM-000000', 92 | dataLayerName: 'PageDataLayer' 93 | } 94 | 95 | TagManager.initialize(tagManagerArgs) 96 | ... 97 | 98 | const app = document.getElementById('app') 99 | ReactDOM.render(, app) 100 | 101 | ``` 102 | 103 | And send your data in each page you want 104 | 105 | ```js 106 | import React from 'react' 107 | 108 | ... 109 | import TagManager from 'react-gtm-module' 110 | 111 | const tagManagerArgs = { 112 | dataLayer: { 113 | userId: '001', 114 | userProject: 'project', 115 | page: 'home' 116 | }, 117 | dataLayerName: 'PageDataLayer' 118 | } 119 | ... 120 | 121 | const Home = () => { 122 | ... 123 | TagManager.dataLayer(tagManagerArgs) 124 | ... 125 | 126 | return ( 127 |
128 | //your component code 129 |
130 | ) 131 | } 132 | 133 | export default Home 134 | 135 | ``` 136 | 137 | 138 | ## Events 139 | 140 | ### Example: 141 | 142 | ```js 143 | import React from 'react' 144 | import ReactDOM from 'react-dom' 145 | import Router from 'react-router' 146 | import routes from './routes' 147 | 148 | ... 149 | import TagManager from 'react-gtm-module' 150 | 151 | const tagManagerArgs = { 152 | gtmId: 'GTM-000000', 153 | events: { 154 | sendUserInfo: 'userInfo' 155 | } 156 | } 157 | 158 | TagManager.initialize(tagManagerArgs) 159 | ... 160 | 161 | const app = document.getElementById('app') 162 | ReactDOM.render(, app) 163 | ``` 164 | 165 | ## Environments 166 | 167 | Configure how Tag Manager will works between development and production server environments. 168 | 169 | ### Example: 170 | 171 | ```js 172 | import React from 'react' 173 | import ReactDOM from 'react-dom' 174 | import Router from 'react-router' 175 | import routes from './routes' 176 | 177 | ... 178 | import TagManager from 'react-gtm-module' 179 | 180 | const tagManagerArgs = { 181 | gtmId: 'GTM-000000', 182 | auth: '6sBOnZx1hqPcO01xPOytLK', 183 | preview: 'env-2' 184 | } 185 | 186 | TagManager.initialize(tagManagerArgs) 187 | 188 | ``` 189 | 190 | ##### How can I find auth and preview? 191 | Go to Google Tag Manager -> ADMIN -> Environments -> Actions -> Get Snippet. 192 | Look for gtm_auth and gtm_preview 193 | 194 | ##### Don't know to use GTM environments? 195 | - https://support.google.com/tagmanager/answer/6311518 196 | - https://www.simoahava.com/analytics/better-qa-with-google-tag-manager-environments/ 197 | 198 | 199 | 200 | |Value|Type|Required|Notes| 201 | |------|-----|-----|-----| 202 | |gtmId| `String`| Yes | GTM id, must be something like `GTM-000000`.| 203 | |dataLayer| `Object`| No | Object that contains all of the information that you want to pass to Google Tag Manager.| 204 | |dataLayerName| `String`| No | Custom name for dataLayer object.| 205 | |events| `Object`| No | Additional events such as 'gtm.start': new Date().getTime(),event:'gtm.js'.| 206 | |auth| `String` | No | used to set environments. | 207 | |preview| `String` | No | used to set environments, something like `env-00`. | 208 | 209 | 210 | ### Note: 211 | 212 | - Disabling javascript in the browser can prevent the correct operation of this library if React is only being rendered on the client side. 213 | 214 | - Before implementing GTM in your application ensure that you have at least one published container, otherwise Google Tag Manager snippet will return 404. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-gtm-module", 3 | "version": "2.0.11", 4 | "description": "React Google Tag Manager Module", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "lint": "eslint .", 8 | "jest": "jest", 9 | "test": "npm run lint && npm run jest", 10 | "build": "babel src -d dist --ignore src/__tests__", 11 | "prepublish": "npm run build" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/alinemorelli/react-gtm" 16 | }, 17 | "author": { 18 | "name": "Aline Morelli", 19 | "email": "aline.morelliw@gmail.com", 20 | "url": "https://github.com/alinemorelli" 21 | }, 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/alinemorelli/react-gtm/issues" 25 | }, 26 | "homepage": "", 27 | "keywords": [ 28 | "react", 29 | "reactjs", 30 | "react-component", 31 | "google tag manager", 32 | "tag manager", 33 | "gtm" 34 | ], 35 | "devDependencies": { 36 | "babel-cli": "^6.24.1", 37 | "babel-eslint": "^7.2.3", 38 | "babel-preset-es2015": "^6.24.1", 39 | "babel-preset-react": "^6.24.1", 40 | "babel-register": "6.18.0", 41 | "enzyme": "^3.3.0", 42 | "eslint": "^4.18.2", 43 | "jest": "^19.0.2", 44 | "jest-cli": "^21.2.1", 45 | "sinon": "^1.14.1" 46 | }, 47 | "jest": { 48 | "testRegex": "__tests__.*\\.spec.js$", 49 | "collectCoverage": true 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Snippets.js: -------------------------------------------------------------------------------- 1 | import warn from './utils/warn' 2 | 3 | // https://developers.google.com/tag-manager/quickstart 4 | 5 | const Snippets = { 6 | tags: function ({ id, events, dataLayer, dataLayerName, preview, auth }) { 7 | const gtm_auth = `>m_auth=${auth}` 8 | const gtm_preview = `>m_preview=${preview}` 9 | 10 | if (!id) warn('GTM Id is required') 11 | 12 | const iframe = ` 13 | ` 15 | 16 | const script = ` 17 | (function(w,d,s,l,i){w[l]=w[l]||[]; 18 | w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js', ${JSON.stringify(events).slice(1, -1)}}); 19 | var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:''; 20 | j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl+'${gtm_auth}${gtm_preview}>m_cookies_win=x'; 21 | f.parentNode.insertBefore(j,f); 22 | })(window,document,'script','${dataLayerName}','${id}');` 23 | 24 | const dataLayerVar = this.dataLayer(dataLayer, dataLayerName) 25 | 26 | return { 27 | iframe, 28 | script, 29 | dataLayerVar 30 | } 31 | }, 32 | dataLayer: function (dataLayer, dataLayerName) { 33 | return ` 34 | window.${dataLayerName} = window.${dataLayerName} || []; 35 | window.${dataLayerName}.push(${JSON.stringify(dataLayer)})` 36 | } 37 | } 38 | 39 | module.exports = Snippets 40 | -------------------------------------------------------------------------------- /src/TagManager.js: -------------------------------------------------------------------------------- 1 | import Snippets from './Snippets' 2 | 3 | const TagManager = { 4 | dataScript: function (dataLayer) { 5 | const script = document.createElement('script') 6 | script.innerHTML = dataLayer 7 | return script 8 | }, 9 | gtm: function (args) { 10 | const snippets = Snippets.tags(args) 11 | 12 | const noScript = () => { 13 | const noscript = document.createElement('noscript') 14 | noscript.innerHTML = snippets.iframe 15 | return noscript 16 | } 17 | 18 | const script = () => { 19 | const script = document.createElement('script') 20 | script.innerHTML = snippets.script 21 | return script 22 | } 23 | 24 | const dataScript = this.dataScript(snippets.dataLayerVar) 25 | 26 | return { 27 | noScript, 28 | script, 29 | dataScript 30 | } 31 | }, 32 | initialize: function ({ gtmId, events = {}, dataLayer, dataLayerName = 'dataLayer', auth = '', preview = '' }) { 33 | const gtm = this.gtm({ 34 | id: gtmId, 35 | events: events, 36 | dataLayer: dataLayer || undefined, 37 | dataLayerName: dataLayerName, 38 | auth, 39 | preview 40 | }) 41 | if (dataLayer) document.head.appendChild(gtm.dataScript) 42 | document.head.insertBefore(gtm.script(), document.head.childNodes[0]) 43 | document.body.insertBefore(gtm.noScript(), document.body.childNodes[0]) 44 | }, 45 | dataLayer: function ({dataLayer, dataLayerName = 'dataLayer'}) { 46 | if (window[dataLayerName]) return window[dataLayerName].push(dataLayer) 47 | const snippets = Snippets.dataLayer(dataLayer, dataLayerName) 48 | const dataScript = this.dataScript(snippets) 49 | document.head.insertBefore(dataScript, document.head.childNodes[0]) 50 | } 51 | } 52 | 53 | module.exports = TagManager 54 | -------------------------------------------------------------------------------- /src/__tests__/Snippets.spec.js: -------------------------------------------------------------------------------- 1 | import Snippets from '../Snippets' 2 | 3 | let args 4 | let snippets 5 | 6 | describe('Snippets', () => { 7 | beforeEach(() => { 8 | args = { id: 'GTM-abc123', dataLayerName: 'dataLayer', events: {} } 9 | snippets = Snippets.tags(args) 10 | }) 11 | 12 | it('should use the `id` for the iframe', () => { 13 | expect(snippets.iframe).toContain(`id=${args.id}`, 1) 14 | }) 15 | 16 | it('should use the `gtm_auth` and `gtm_preview` for the iframe', () => { 17 | Object.assign(args, { 18 | auth: '6sBOnZx1hqPcO01xPOytLK', 19 | preview: 'env-2' 20 | }) 21 | snippets = Snippets.tags(args) 22 | expect(snippets.iframe).toContain(`gtm_auth=${args.auth}`, 1) 23 | expect(snippets.iframe).toContain(`gtm_preview=${args.preview}`, 1) 24 | }) 25 | 26 | it('should use the `dataLayer` for the script', () => { 27 | args = { dataLayer: { name: 'test' } } 28 | const dataLayerName = 'dataLayer' 29 | snippets = Snippets.dataLayer(args, dataLayerName) 30 | console.log(snippets) 31 | expect(snippets).toContain('{"name":"test"}') 32 | }) 33 | 34 | it('no id provided should log a warn', () => { 35 | console.warn = jest.fn() 36 | const noIdArgs = { dataLayerName: 'dataLayer', events: {} } 37 | Snippets.tags(noIdArgs) 38 | expect(console.warn).toBeCalled() 39 | }) 40 | }) -------------------------------------------------------------------------------- /src/__tests__/TagManager.spec.js: -------------------------------------------------------------------------------- 1 | import TagManager from '../TagManager' 2 | 3 | describe('TagManager', () => { 4 | it('should render tagmanager', () => { 5 | TagManager.initialize({gtmId: 'GTM-000000'}) 6 | expect(window.dataLayer).toHaveLength(1) 7 | }) 8 | 9 | it('should render datalayer', () => { 10 | const gtmArgs = { 11 | gtmId: 'GTM-000000', 12 | dataLayer: { 13 | userInfo: 'userInfo' 14 | } 15 | } 16 | TagManager.initialize(gtmArgs) 17 | expect(window.dataLayer).toHaveLength(1) 18 | }) 19 | }) -------------------------------------------------------------------------------- /src/__tests__/warn.spec.js: -------------------------------------------------------------------------------- 1 | import sinon from 'sinon' 2 | import warn from '../utils/warn' 3 | 4 | describe('warn()', function () { 5 | it('should append [react-gtm] to warning messages', () => { 6 | sinon.stub(console, 'warn') 7 | warn('foo') 8 | console.warn.toHaveBeenCalled 9 | }) 10 | }) -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import TagManager from './TagManager' 2 | 3 | module.exports = TagManager -------------------------------------------------------------------------------- /src/utils/warn.js: -------------------------------------------------------------------------------- 1 | const warn = (s) => { 2 | console.warn('[react-gtm]', s); 3 | } 4 | 5 | export default warn --------------------------------------------------------------------------------