I followed the public react app
simple-reactjs-app
by aditya-sridhar found on Github for setting up the web application. I cloned the repo and just added two blocks (timer init & HTML timer update div) so that you can follow up if you wanted to reproduce the same. I also integrated the timeonsite.js tracker as given below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- ADDED FOR Real-time time tracking -->
<script type="text/javascript">
var Tos;
(function(d, s, id, file) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s);
js.id = id;
js.onload = function() {
var config = {
trackBy: 'seconds',
developerMode: true,
callback: function(data) {
console.log(data);
// give your endpoint URL/ server-side URL that is going to handle your TOS data which is of POST method. Eg. PHP, nodejs or python URL which saves this data to your DB
var endPointUrl = 'http://localhost/tos'; // replace with your endpoint URL
if (data && data.trackingType && data.trackingType == 'tos') {
if (Tos.verifyData(data) != 'valid') {
console.log('Data abolished!');
return;
}
if (navigator && typeof navigator.sendBeacon === 'function') {
var blob = new Blob([JSON.stringify(data)], {type : 'application/json'});
navigator.sendBeacon(endPointUrl, blob);
}
}
}
};
if(TimeOnSiteTracker) {
Tos = new TimeOnSiteTracker(config);
window.Tos = Tos;
}
};
js.src = file;fjs.parentNode.insertBefore(js, fjs);
} (document, 'script', 'TimeOnSiteTracker', '//cdn.jsdelivr.net/gh/saleemkce/[email protected]/timeonsitetracker.min.js'));
</script>
<!-- ADDED FOR Real-time time tracking -->
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!-- ADDED FOR Real-time time tracking -->
<div id="timeContainerStats" style="border: 1px solid #bbb; border-radius: 10px; text-align: center; font-family: cursive;">
You spent :
<span id="timeonsite" style="color:#f55;">
</span> Seconds
</div>
<script>
console.log(typeof window)
if (typeof window !== 'undefined') {
setInterval(function() {
document.getElementById('timeonsite').innerHTML = (window.Tos).getTimeOnPage().timeOnPage;
}, 1000)
}
</script>
<!-- ADDED FOR Real-time time tracking -->
</body>
</html>
Sorry, my first post here. So, I'm not allowed to show image directly hence image is added as link.
Questions:
1, I'm not allowed to access Tos window object directly. Hence I accessed it like "window.Tos" in React root index.html Is it the right approach to fetch third party object in React?
2, Is it proper way to include the HTML block,
<span id="timeonsite" style="color:#f55;">
</span> Seconds
or the right way would be write it as React component?
3, I added the powerful
setInterval(function() {}, 1000);
JS Interval function. Is it fine or we can use React inbuilt timer function for updating TIMER "div" each second?
FYI, I will need this Tos JS object in every page in my React web app so that I show how much time the user spent in the app so far in web page. If it can be written as React component, could you show a sample component written for this live time-counter. I've seen one other question in SO raising the same question but that used JS date timer directly instead of full-fledged tracker like timeonsite.js. Thanks for your help.
Let's try to give you some answers:
I'm not allowed to access Tos window object directly. Hence I accessed it like "window.Tos" in React root index.html Is it the right approach to fetch third party object in React?
As long as you declare Tos
as a global variable you should be able to access it from all the scopes. But more importantly there are several ways of achieving what you want.
To add a third-party object in React you can:
index.html
file, the approach you took. The issue here is that then to have some communication between the third party library and your react code it can only happen through global variables which could be a bad pattern.onMount
the script:function App() {
// Proof that we can use Tos object
const click = () => {
console.log('Click', Tos)
Tos
}
useEffect(() => {
// This is the same that your script does but in a "React" way
const script = document.createElement('script')
script.setAttribute('src', '//cdn.jsdelivr.net/gh/saleemkce/[email protected]/timeonsitetracker.min.js')
script.id = 'TimeOnSiteTracker'
script.addEventListener('load', () => {
const config = {
trackBy: 'seconds',
developerMode: true,
callback: function(data) {
console.log(data);
}
};
if(TimeOnSiteTracker) {
// IF this was not a `var` it will not be a global scoped variable
var Tos = new TimeOnSiteTracker(config);
window.Tos = Tos;
}
})
document.body.appendChild(script)
}, [])
return (
<div className="App">
<header className="App-header">
<button onClick={click}>Click</button>
</header>
</div>
);
}
Using useEffect
we can basically mount the 3rd-party library on the mount lifecycle. In this last approach, you have more control over the parameters that you pass to the library, they can even be more dynamic and easier to interact as you will see in the answer to your 3rd question.
Is it proper way to include the HTML block, or the right way would be write it as React component?
To get the power of react (rendering, performance control over components) the best thing will be to move this code into the JSX
of your app. This way this element will be dynamically added and not hard coded as in your approach.
I added the powerful setInterval(function() {}, 1000); JS Interval function. Is it fine or we can use React inbuilt timer function for updating TIMER "div" each second?
Actually given the API of Timeonsite.js, there is no better way of doing this (some may think that callback
argument is called every time there is an update, but no, only on start and onload. Also, if we check what the developermode
does, it basically calls Tos every second using the interval:
if (this.developerMode) {
setInterval(function() {
self.showProgress();
}, (1 * 1000));
}
To do this in a React friendly way:
const [timer, setTimer] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
console.log(window.Tos.getTimeOnPage().timeOnPage)
setTimer(window.Tos.getTimeOnPage().timeOnPage)
}, 1000);
return () => clearInterval(interval);
}, []);
And then in the JSX:
<div>You spent: <span> { timer }</span> seconds</div>
So if you declare it in your root component like this:
import './App.css';
import { useEffect, useState} from "react";
function App() {
const [timer, setTimer] = useState(0)
useEffect(() => {
const interval = setInterval(() => {
setTimer(window.Tos.getTimeOnPage().timeOnPage)
}, 1000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const script = document.createElement('script')
script.setAttribute('src', '//cdn.jsdelivr.net/gh/saleemkce/[email protected]/timeonsitetracker.min.js')
script.id = 'TimeOnSiteTracker'
script.addEventListener('load', () => {
const config = {
trackBy: 'seconds',
};
if (TimeOnSiteTracker) {
var Tos = new TimeOnSiteTracker(config);
window.Tos = Tos;
}
})
document.body.appendChild(script)
}, [])
return (
<div className="App">
<header className="App-header">
<div>
You spent : <span>{timer} </span> Seconds
</div>
</header>
</div>
);
}
You can get a timer. Then it is a matter of playing in how you access the state in all your pages and properly configuring Tos to be tracked in all the pages you want.