0

I am working on an app that needs to count the number of users that are viewing a webpage at the same time. To do that, I created a React component that is responsible for the page display, and every time when it loads it sends a request to the Node.JS backend whose sole purpose is to keep track of the amount of users. Every time the page loads, it sends the get request to Node to get the amount of users and displays it as a state, and every time the user leaves the page the event listener must send a post request to decrement the value of variable by one. This is what I have so far:

server.js (back-end)

//This is a small backend that keeps track of how many people are on each page.
const express = require('express')
const cors = require('cors')
const app = express()

const whiteList = ['http://localhost:3000', 'http://127.0.0.1:3000']
const corsOptions = {
    origin: function (origin, callback) {
      if (!origin || whiteList.indexOf(origin) !== -1)
          callback(null, true)
      else
        callback(new Error("Not allowed by CORS"))
    },
    credentials: true,
  }

app.use(cors(corsOptions))
let googleCounter = 0, discordCounter = 0, linuxCounter = 0
console.log(googleCounter, discordCounter, linuxCounter)
app.get("/google", (request, response) => {
    console.log(googleCounter, discordCounter, linuxCounter)
    console.log(`Google counter ${googleCounter} increased to ${googleCounter + 1}`)
    googleCounter++
    response.send(`Google Counter: ${googleCounter}`)
})

app.get("/discord", (request, response) => {
    discordCounter++
    response.send(discordCounter)
})

app.get("/linux", (request, response) => {
    linuxCounter++
    response.send(linuxCounter)
})

//These methods will fire when users leave the pages.
app.post("/google", () => {
    console.log(`Google counter ${googleCounter} decreased to ${googleCounter - 1}`)
    googleCounter--})
app.post("/discord", () => discordCounter--)
app.post("/linux", () => linuxCounter--)

app.listen(5000, () => console.log(`Server started on port 5000: ${googleCounter}, ${discordCounter}, ${linuxCounter}`))

Google.js (the front-end)

//This is a generic page to view. Source: https://uk.wikipedia.org/wiki/Google
import React, {useState, useEffect} from "react"
import Base from "./Base"

export default function Google(props) {
    const url = "http://localhost:5000/google"
    const [counter, setCounter] = useState(1)
    //Increment user counter when the page is loaded:
    useEffect(() => {
        if (!props.user) return;
        fetch(url, {method: "GET"})
            .then(response => {return response.text()})
            .then(response => console.log(response))
            .catch(error => console.log(error))
         //Decrement user counter when the user leaves the page:
         window.addEventListener("unload", () => fetch(url, {method: "POST"})
            .then(response => {return response.text()})
            .then(response => console.log(response))
            .then(() => console.log("User left the page")))
            .catch(error => console.log(error))
    }, [props.user])

    if (!props.user) return (<div><Base/></div>)
    return (<div>
        <h1>Google</h1>
        <h2>Currently active users: {counter}</h2>
    </div>)
}

My issue is that the googleCounter variable behaves unpredictably: when I run the server, it immediately falls to -3 to -5, and every time I send the get request it increments the variable but also decrements. I suspect the unload event could run more often than when user leaves the page, useEffect could have run more often or the get request could overlap with the post request in some way. When I tried to log the post request from the client-side, I've got the event listener is undefined: enter image description here

How can I make sure that the googleCounter variable only decreases when the user leaves the page? enter image description here

  • *every time the user leaves the page the event listener must send a post request to decrement the value of variable by one* - you won't be able to do that – Konrad Dec 30 '22 at 19:07
  • 1
    useEffect runs twice in development mode in react as a precaution. – Benjamin Gruenbaum Dec 30 '22 at 19:07
  • 1
    You also never clear the listener when you should – Konrad Dec 30 '22 at 19:08
  • @Konrad can you tell then how can user counting be implemented then? I also don't clear the even listener because they run in `unload` event when the page is closed, so they are removed automatically, aren't they? – Владислав Король Dec 30 '22 at 19:09
  • 3
    I recommend either a websocket connection or [`navigator.sendBeacon`](https://stackoverflow.com/a/51022465/1348195) for actually knowing when the user leaves the page. As Konrad said, sending AJAX requests on unload is unreliable. (Additionally, even sendBeacon isn't 100% reliable - what if the user unplugs their computer or loses internet access?) – Benjamin Gruenbaum Dec 30 '22 at 19:10
  • @ВладиславКороль the problem is that you've added more than one `unload` event listener and all of them will run, so potentially you will send multiple requests – Konrad Dec 30 '22 at 19:11
  • 1
    Thanks @Konrad and @Benjamin Gruendaum! What you told works completely, using `navigator.sendBeacon()` and removing the event listeners solved the problem. – Владислав Король Jan 04 '23 at 14:16

0 Answers0