Search code examples
javascriptreactjstime-tracking

Integrating timeonsite.js on a basic ReactJS app and correct way to show live time counter for tracking user engagement


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>

Live Timer in React App

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.


Solution

  • 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:

    • add it as you did in the 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.
    • Using some kind of webpack plugin for your cdn. But this will present the same issue as the previous approach. It just looks cleaner.
    • Add 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.