How to execute axios code using the window's beforeunload event in React

1.5k views Asked by At

I'm using React and PHP, and I need it to do something specific. I'm using Axios to send requests to my PHP pages which then change my database. I need to make an update to my MySQL database table that changes the is_logged value from true to false if the user closes the page or the browser. The code to do this is set in the window's beforeunload event. However, the database is never updated. Is what I'm trying to do even possible in React?

Here's my React component code:

import React, { useState, useEffect } from 'react';
import axios from 'axios';
import Viewers from './Viewers';

const Live = ({ match }) => {

  window.addEventListener("beforeunload", () => {
    axios.get('http://localhost/live-streaming-app/get_viewers.php?=' + token)
      .then(response => console.log(response))
  })

  // Set token from url
  const token = match.params.token

  //Get the fullname and update logged_in to true
  const [fullname, setFullname] = useState("")

  useEffect(() => {
    axios.get('http://localhost/live-streaming-app/get_user.php?token=' + token)
      .then(response => {
        setFullname(response.data.fullname)
        console.log("Data", response.data)
      })
      .catch(error => console.log(error))

    return () => {
      axios.get('http://localhost/live-streaming-app/get_viewers.php?=' + token)
        .then(response => console.log(response))
        .catch(error => console.log(error))
    }
  }, [token])

  //Jsx for when user is unauthorised
  const rejected = (
    <div className="container text-center pt-5">
      <h1>Rejected Connection</h1>
      <p>You are unauthorised to view this page</p>
    </div>
  )

  let my_render = ""

  if (fullname && fullname != null) {
    my_render = <Viewers fullname={fullname} />
  } else {
    my_render = rejected
  }

  return my_render
};

export default Live;

Here is the PHP page that is called:

<?php
require 'connect.php';

$token = $_GET['token'];

$sql = "UPDATE `viewers` SET `logged_in`='false' WHERE `live_token`='{$token}'";
if(mysqli_query($con, $sql)){
  http_response_code(201);
}
else {
  http_response_code(422);
}

exit;
2

There are 2 answers

0
Lajos Arpad On BEST ANSWER

Issues to deal with:

Is the request being sent at all?

Try running

axios.get('http://localhost/live-streaming-app/get_viewers.php?=')
  .then(response => console.log(response))

in your browser console and see whether anything is logged in the console. Check the Network tab as well in your dev tools to see whether there was a request sent. If the request is sent at all, then the sending of the request works. If not, then you have some issues with the URL that you will need to fix.

The token

Sending this request

axios.get('http://localhost/live-streaming-app/get_viewers.php?=' + token)
  .then(response => console.log(response))

Says that empty string will have the value of token. On the other hand, your PHP code assumes that there is a parameter called token:

<?php
require 'connect.php';

$token = $_GET['token']; //This is the line which assumes that a parameter called token exists

$sql = "UPDATE `viewers` SET `logged_in`='false' WHERE `live_token`='{$token}'";
if(mysqli_query($con, $sql)){
  http_response_code(201);
}
else {
  http_response_code(422);
}

exit;

To comply with the assumption of your PHP code, you will need to specify that token is the value of a parameter called token:

  window.addEventListener("beforeunload", () => {
    axios.get('http://localhost/live-streaming-app/get_viewers.php?token=' + token)
      .then(response => console.log(response))
  })

The fix above should solve the issue you are asking about, but let's not stop here.

SQL Injection

What if I send this request (don't run it, unless you create a backup) from my browser console (or cURL):

axios.get("http://localhost/live-streaming-app/get_viewers.php?token=';delete from viewers where ''='")
  .then(response => console.log(response))

?

Your PHP code then would execute this:

UPDATE `viewers` SET `logged_in`='false' WHERE `live_token`='';delete from viewers where ''=''

removing all viewers from your database. Use PDO to parameterize your queries and making your queries safe from such attacks.

Summary

React is a Javascript framework, which means that whatever Javascript is capable of your client-side is also capable of. It is true that some features are not necessarily available in the React way, but that should not worry you about those being possible, assuming that Javascript is capable of that. I tend to think that client-side frameworks will become less than useful when many things are going on in the client-side and I think that the main reason of popularity of client-side frameworks is that most programmers do not realize of just how many things Javascript is capable of. I'm not saying that client-side frameworks should never be used. I'm only saying that I have seen programmers being blind fans of client-side frameworks and technologies, which ended up destroying the project they worked on. So, when you choose your client-side tech stack, it is worth to check the capabilities your client-side requires, the time needed to implement it without client-side frameworks and the time needed to implement it using each framework that you might consider using. Compare the two and think about deadlines and financial capabilities. If you can not find an obvious, very good reason to use a client-side framework, then I advise against the use of it. Because, at the end of the day, if during development it turns out that for some reason you need to use a client-side framework, you can easily shift to it from the position of not using a framework. However, if you are using a client-side framework and it turns out to be unfeasible for your project, then refactoring the whole project not to use that framework often means reimplementing most of your client-side. And this kind of problem more often then not reveals itself in urgencies.

0
Saddam On

There is no guarantee that asynchronous actions executed in the beforeunload event will complete, and axios uses an asynchronous way of making requests.

You can probably use good old XHR to make a synchronous request. If you go to the section labeled Adapting Sync XHR use cases to the Beacon API, they will go over strategies of keeping requests alive during unload because synchronous XHR is deprecated.

Note that synchronous HTTP requests are generally a bad idea because they will render the page unresponsive while they are completing. I'm not sure what the behavior would be for a synchronous request during unload.

Example of Synchronous Request

logUser () {
    var params = JSON.stringify({ locked_by: '' });
    let xhr = new XMLHttpRequest()
    xhr.open('PUT',Url, false) // `false` makes the request synchronous
    xhr.setRequestHeader("Authorization", 'Bearer ' + localStorage.getItem('app_access_token'))
    xhr.setRequestHeader("Content-length", params.length);
    xhr.setRequestHeader("Content-type", "application/json; charset=utf-8")
    xhr.responseType = 'arraybuffer'
    xhr.onreadystatechange = function() {//Call a function when the state changes.
    if(xhr.readyState == 4 && xhr.status == 200) {
        alert(xhr.responseText);
    }
}

Note

There is no reliable way to execute something on tab/window close. It is against the very principle of "closing" the tab i.e. freeing up resources.

The way I would handle your situation is to have the frontend hold something that dies on it own when the tab closes.

Either open a websocket which you can use for many other purposes, and when it dies and does not come back within a few seconds you know that the client is disconnected, or send a regular ping while the tab is open and when it has missed a few pings you can safely assume the client is disconnected.