Adding Google Analytics 4 (GA4) Tracking to React App

Install the React GA4 library

npm i react-ga4

On the top level initialize the tracking ID:

import React, { Suspense } from 'react';
import { BrowserRouter } from 'react-router-dom';

import Routes from './Routes';

import ReactGA from 'react-ga4';
const TRACKING_ID = "G-XXXXXXXXXX"; // OUR_TRACKING_ID
ReactGA.initialize(TRACKING_ID);

function App() {
	return (
		<Suspense fallback={<div>Loading...</div>}>
			<BrowserRouter>
				<Routes ReactGA={ReactGA} />
			</BrowserRouter>
		</Suspense>
	);
}
export default App;

Track pages on Routes.js, then start the Google Event Tracker:

import React, { useEffect, lazy } from 'react';
import {Route, Routes as Switch, useLocation} from 'react-router-dom';
import GoogleAnalyticsEventTracker from './components/GoogleAnalyticsEventTracker/GoogleAnalyticsEventTracker';
 
const Home = lazy(() => import('./pages/Home/Home'));
const Page1 = lazy(() => import('./pages/Page1/Page1'));
const Page2 = lazy(() => import('./pages/Page2/Page2'));
const NotFound = lazy(() => import('./pages/NotFound/NotFound'));


const Routes = props => {
	const url = useLocation();
	useEffect(() => {
		document.title = url.pathname;
		props.ReactGA.pageview(url.pathname + url.search);
	}, [url.pathname]);
	const gaEventTracker = GoogleAnalyticsEventTracker('all_custom_events');

	return (
		<Switch>
			<Route path='/' element={<Home gaEventTracker={gaEventTracker.bind(this)}/>} />
			<Route path='/page1' element={<Page1 gaEventTracker={gaEventTracker.bind(this)}/>} />

			<Route path='/page2/*' element={<Page2 gaEventTracker={gaEventTracker.bind(this)}/>} />

			<Route element={<NotFound/>} />
		</Switch>
	);
}
export default Routes;

Inside your page you’ll add an event for any click events, functions that finish, etc.

import React from 'react'

const Page1 = props => {
	const gaEventTracker = props.gaEventTracker;
    return ( 	
	<button 
		className="button" 
		onClick={e => {
			gaEventTracker('click_button');
		}}
	>
		Click button
	</button>
    );  
}  
export default Page1

How to set state with dynamic key-value pair in React

import React, { useEffect, useState } from 'react'

const Form = props => {
	const [state, setState] = React.useState({});
	function handleFieldChange(event) {
		setState({ ...state, [event.target.name]: event.target.value });
	}
    return (  
	<div className="form">
            <div className="row g-3 mb-3">
                <div className="col">
                    <input name="firstname" onChange={handleFieldChange} />
                </div>
                <div className="col">
                    <input name="email" onChange={handleFieldChange} />
                </div>
            </div>
            <div className="row g-3 mb-3">
                    <div className="col">
                        <textarea name="message" onChange={handleFieldChange}></textarea>
                </div>
            </div>
        </div>
    );  
}
export default Form

Create JSX Project

Create React folder in www with this code:

# Create a project folder
mkdir jsx-modules
cd jsx-modules

# Create an npm project without prompts (-y)
npm init -y

# Install babel
npm install @babel/cli @babel/core @babel/plugin-transform-react-jsx

Next we’ll need a .babelrc which will tell babel how to process our jsx.

{
  "plugins": [
    ["@babel/plugin-transform-react-jsx", { "pragma": "createElement", "pragmaFrag": "'fragment'" }]
  ],
  "comments": false
}

And then add an index.jsx file

// A jsx pragma method to create html dom elements
function createElement(tag, props, ...children) {
	if (typeof tag === "function") return tag(props, children)
	const element = document.createElement(tag)

	Object.entries(props || {}).forEach(([name, value]) => {
		if (name.startsWith('on') && name.toLowerCase() in window)
			element.addEventListener(name.toLowerCase().substr(2), value)
		else element.setAttribute(name, value.toString())
	})

	children.forEach((child) => {
		appendChild(element, child)
	})

	return element
}
const appendChild = (parent, child) => {
	if (Array.isArray(child))
		child.forEach((nestedChild) => appendChild(parent, nestedChild))
	else
		parent.appendChild(
			child.nodeType ? child : document.createTextNode(child)
		)
}
const createFragment = (props, ...children) => {
	return children
}

// Setup some data
const name = 'Geoff'
const friends = ['Sarah', 'James', 'Hercule']

// Create some dom elements
const app = (
  <div className="app">
    <h1 className="title"> Hello, world! </h1>
    <p> Welcome back, {name} </p>
    <p>
      <strong>Your friends are:</strong>
    </p>
    <ul>
      {friends.map(name => (
        <li>{name}</li>
      ))}
    </ul>
  </div>
)

// Render our dom elements
window.document.getElementById('app').replaceWith(app)

Now you can run this command to generate your javascript.

npx babel index.jsx -d dist

Which will create a new dist/index.js file with the transpilled code.

You can add // eslint-disable-next-line no-unused-vars to ignore annoying eslint errors

And to test our code, add an index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>jsx WITHOUT react</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/gh/kognise/water.css@latest/dist/light.min.css"
    />
  </head>
  <body>
    <div id="app"></div>
    <script src="dist/index.js"></script>
  </body>
</html>

I snuck in water.css to make raw html prettier.

Create React Project

Create React folder in www with this code:

create-react-app react-modules

Once all the necessary files are installed, change directory into react-modules and start the application with:

npm start

Build the files ready to go to production:

npm build


We are creating buttons that open a modal window where the content of the modal window changes with each button.

Add this line to dependencies in package.json

"axios": "^0.24.0",

Update packages with this command: npm update

Open App.js

import React from "react";
import axios from "axios";
import "./styles.css";
class App extends React.Component {
	state = {
		show: false,
		content: "",
		title: "",
	};
	removeHTML(string){
		const regex = /(<([^>]+)>)/ig;
		const result = string.replace(regex, '');
		return result;
	}
	showModal = getContent => {
		if (typeof getContent === 'string' || getContent instanceof String){
			axios({
				method: "get",
				url: "http://api.tvmaze.com/search/shows?q="+getContent,
			}).then((response) => {
				let movies = response.data;
				let match = false;
				{movies.map((movie,i) => {
					if( movie.show.language === "English" && match === false ){
						match = true;
						this.setState({
							title: movie.show.name,
							content: this.removeHTML(movie.show.summary)
						});
					}
				})}
			}, (error) => {
				console.log(error);
			});
		}else{
			this.setState({
				content: "",
				title: "",
			});
		}
		this.setState({
			show: !this.state.show
		});
	};
	render() {
		return (
			<div className="App">
				<button
					className="toggle-button centered-toggle-button"
					onClick={e => {
						this.showModal("The Golden Girls");
					}}
				>
					Golden Girls
				</button>
				<button
					className="toggle-button centered-toggle-button"
					onClick={e => {
						this.showModal("Jane The Virgin");
					}}
				>
					Jane The Virgin
				</button>
				{this.state.show && (
					<div className="modal" id="modal">
						<h2>{this.state.title}</h2>
						<div className="content">
							{this.state.content}
						</div>
						<div className="actions">
						  <button className="toggle-button" onClick={this.showModal}>
							close
						  </button>
						</div>
					</div>
				)}
			</div>
		);
	}
}
export default App;

Add modal css to styles.css

.App {
  font-family: sans-serif;
  text-align: center;
}
.modal {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: white;
  border: 1px solid #ccc;
  transition: 1.1s ease-out;
  box-shadow: -2rem 2rem 2rem rgba(0, 0, 0, 0.2);
}
.modal h2 {
  border-bottom: 1px solid #ccc;
  padding: 1rem;
  margin: 0;
}
.modal .content {
  padding: 1rem;
}
.modal .actions {
  border-top: 1px solid #ccc;
  background: #eee;
  padding: 0.5rem 1rem;
}
.modal .actions button {
  border: 0;
  background: #78f89f;
  border-radius: 5px;
  padding: 0.5rem 1rem;
  font-size: 0.8rem;
  line-height: 1;
}
button.centered-toggle-button {
  margin: 0 10px;
}
button {
	cursor: pointer;
}