Home > Back-end >  Race condition in React; API calls, database call
Race condition in React; API calls, database call

Time:12-06

I have a page in a site that is meant to show API data for three APIs. When I click on this page, the first two APIs load, but the third does not.

Each API uses a set of parameters. For example, the first API has a platform, region, and username in the url. The parameters are stored in a firestore database that I have created for this project.

When I began this script, I initially hardcoded the parameters, and all three APIs showed up no problem. However, when I began pulling the parameters from the firestore database, I created a race condition.

I have narrowed down my problem to this: this script should run once sequentially, but it now runs infinitely after adding database pulling. This means that each API query is ran more than once. Because there is a delay in getting parameters from the database, the script attempts to run API queries with no parameters (which are thus incorrect). This returns an error and prevents the third API from ever loading.

I have included my script below, as well as screenshots of my site and console.

// import React from "react";
import React, { useState, useEffect, useMemo, useRef } from "react";
// import "./Statsview.css";

import {database} from "./firebase";

import PersonIcon from '@material-ui/icons/Person';
import ForumIcon from "@material-ui/icons/Forum";
import IconButton  from "@material-ui/core/IconButton";
import ArrowBackIosIcon from "@material-ui/icons/ArrowBackIos";
import {Link, useHistory, useParams} from "react-router-dom";
import { People } from "@material-ui/icons";

console.log('starting script');


var O_mostPlayed;
var O_winRatio;
var O_compED;
var O_avgE;

var A_compRank;
var A_arenaRank;
var A_level;
var A_kills;

var C_wins;
var C_losses;
var C_winRate;


function Statsview( {backButton} ) {
    // getting api info from our db
    const urlParams = new URLSearchParams(window.location.search);
    const name = urlParams.get('name');
    const chessName = urlParams.get('chess');

    const [game1, setgame1] = useState("");
    const [game2, setgame2] = useState("");
    const [game3, setgame3] = useState("");

    const [O_name, setO_name] = useState("");
    const [O_platform, setO_platform] = useState("");
    const [O_region, setO_region] = useState("");
    const [A_name, setA_name] = useState("");
    const [A_platform, setA_platform] = useState("");
    const [C_name, setC_name] = useState("");

    const [didO, setset_didO] = useState(false);
    const [didA, setset_didA] = useState(false);
    const [didC, setset_didC] = useState(false);

    
    useEffect(() => {
        database.collection('USERS').where('name','==',name).get().then(snapshot => {
            snapshot.forEach(doc => {
                const data = doc.data()
                setgame1(data.game1);
                setgame2(data.game2);
                setgame3(data.game3);
                setO_name(data.overwatch);
                setO_platform(data.overwatchPlatform);
                setO_region(data.overwatchRegion);
                setA_name(data.apexLegends);
                setA_platform(data.apexLegendsPlatform);
                // setC_name(data.chess);
            })
        }).catch(error => console.log(error))      
    },[]);

    var O_url = 'https://owapi.io/stats/'   O_platform   '/'   O_region   '/'   O_name;
    var A_url = 'https://api.mozambiquehe.re/bridge?version=5&platform='   A_platform   '&player='   A_name   '&auth=...';
    // var C_url = 'https://api.chess.com/pub/player/'   chessName   '/stats';
    var C_url = 'https://api.chess.com/pub/player/cnewby5283/stats';
    console.log(C_url);

    // console.log(O_url)
    var request_O = new XMLHttpRequest();
    var request_A = new XMLHttpRequest();
    var request_C = new XMLHttpRequest();
    // API queries
    request_O.open('GET', O_url, true);
    request_A.open('GET', A_url, true);
    request_C.open('GET', C_url, true);
    // potential API queries
    // request.open('GET', 'https://api.clashroyale.com/v1/players/#9CCUURQVJ', true);
    // request.setRequestHeader("authorization","Bearer [api key for specific IP]");
    console.log('got queries');

    if (O_name.localeCompare("") != 0 && O_platform.localeCompare("") != 0 && O_region.localeCompare("") != 0) {
        console.log("O_url: ", O_url)
        request_O.onload = function () {
            var data = JSON.parse(this.response);
            if (request_O.status >= 200 && request_O.status < 400) {
                O_mostPlayed = "Most Played: "    data.stats.top_heroes.competitive.played[0].hero   ", "   data.stats.top_heroes.competitive.played[1].hero   ", "   data.stats.top_heroes.competitive.played[2].hero;
                O_winRatio = "Win Ratio: "    (parseInt(data.stats.game.competitive[3].value) / parseInt(data.stats.game.competitive[1].value)).toFixed(3);
                O_compED = "Competitive Elims/Deaths: "    (parseInt(data.stats.combat.competitive[4].value) / parseInt(data.stats.combat.competitive[3].value)).toFixed(3);
                O_avgE = "Avg. Eims / 10 Minutes: "    data.stats.average.competitive[3].value;
                document.getElementById("O_1").innerHTML = O_mostPlayed;
                document.getElementById("O_2").innerHTML = O_winRatio;
                document.getElementById("O_3").innerHTML = O_compED;
                document.getElementById("O_4").innerHTML = O_avgE;
            } else {
                return (
                    <marquee>
                        API Request Failed
                    </marquee>
                )
            }
            // didO = true;
        }

        request_O.send();
    }
    if (A_name.localeCompare("") != 0 && A_platform.localeCompare("") != 0) {
        console.log("A_url: ", A_url)
        request_A.onload = function () {
            var data = JSON.parse(this.response);
            if (request_O.status >= 200 && request_O.status < 400) {
                A_compRank = "Competitive Rank : "   data.global.rank.rankName   " -- "   data.global.rank.rankScore;
                A_arenaRank = "Arena Rank : "   data.global.arena.rankName   " -- "   data.global.arena.rankScore;
                A_level = "Level : "   data.global.level;
                A_kills = "Kills : "   data.total.kills.value;
                document.getElementById("A_1").innerHTML = A_compRank;
                document.getElementById("A_2").innerHTML = A_arenaRank;
                document.getElementById("A_3").innerHTML = A_level;
                document.getElementById("A_4").innerHTML = A_kills;
                
            } else {
                return (
                    <marquee>
                        API Request Failed
                    </marquee>
                )
            }
            // didA = true;
        }
        request_A.send();
    }
    if (chessName.localeCompare("") != 0) {
        console.log("C_url: ", C_url)
        request_C.onload = function () {
            // console.log("test chess query below 1");
            var data = JSON.parse(this.response);
            if (request_O.status >= 200 && request_O.status < 400) {
                console.log("entered c if statement")
                C_wins = "Wins : "   data.chess_rapid.record.win;
                C_losses = "Losses : "   data.chess_rapid.record.loss;
                C_winRate = "Win rate : "   (parseInt(data.chess_rapid.record.win) / parseInt(data.chess_rapid.record.loss)).toFixed(3);
                document.getElementById("C_1").innerHTML = C_wins;
                document.getElementById("C_2").innerHTML = C_losses;
                document.getElementById("C_3").innerHTML = C_winRate;
                
            } else {
                console.log("didnt enter c if statement")
                return (
                    <marquee>
                        API Request Failed
                    </marquee>
                )
            }
            // didC = true;
        }
        
        request_C.send();
    }

    return(
        <div class="apiDisplays">
            <div id="nameDisplay">
                Meet {name}!<br/><br/>
            </div>
            <div id="gamesPlayedDisplay">
                {/* show games */}
                {name} plays these games: <br/>
                {game1} <br/> {game2} <br/> {game3}
                <br/><br/>
            </div>
            <div id="apiDisplay">
                {/* shows api data */}
                Avaliable stats for {name} <br/><br/>
            </div>
            <div>
                <h1>Overwatch Stats</h1>
                <p id="O_1">None</p>
                <p id="O_2">None</p>
                <p id="O_3">None</p>
                <p id="O_4">None</p> 
            </div>
            <div>
                <h1>Apex Stats</h1>
                <p id="A_1">None</p>
                <p id="A_2">None</p>
                <p id="A_3">None</p>
                <p id="A_4">None</p>
            </div>
            <div>
                <h1>Chess.com Stats</h1>
                <p id="C_1">None</p>
                <p id="C_2">None</p>
                <p id="C_3">None</p>
            </div>
        </div>
    )
}

export default Statsview

enter image description here


Edit: I included if statements before each API call, and now I am not getting any errors.

The third API still does not show, however. This is interesting because the url that I send in the XMLHTTPRequest is correct, as shown in the screenshot. (I output the url in the third if statement).

If I am sending the correct url for the API query, why is my program returning that the status of this query is not sufficient?

enter image description here

CodePudding user response:

As it is, your API calls are being performed every time the component is rendered because they're in the body of the function. React components are just normal old functions, so every time React renders your component (read: every time React calls your function), your component will run all of the API calls regardless of the status of the parameters. That's bad! At the very least, you should wrap your API calls in a useEffect and test that you already have the parameters that you need before you fetch from the API:

// this is pseudocode, hopefully you get the idea
    
React.useEffect(() => {
  if (dbParameters) {
  // do your fetching
  }
}, [dbParameters]);

You're at least sort of familiar with this concept because you already do it for the db parameters!

I highly recommend reading through this article a couple of times, it's an invaluable resource when dealing with useEffect and fetching data.

  • Related