Search code examples
javascriptreactjstypeerrorfetch-apireact-component

TypeError: Failed to fetch & Cannot read properties of undefined


I'm working on a React project and my fetch() function is acting weird. In the previous use I made of it, it worked perfectly fine; now that I'm trying to fetch to a different URL (same domain) with the same headers in the request, the function is acting weird. What I'm trying to do is basically to retrieve a JSON that contains some data, store it in the component's attributes and then render it. Using Postman at the very same URL with the very same headers, I'm perfectly able to receive such response just fine, but in VisualStudio, the error I'm getting is:

There was an error: TypeError: Failed to fetch

as well as :

 The above error occurred in the <MatchList> component:

    at MatchList (http://localhost:3000/static/js/bundle.js:814:5)
    at div
    at ButtonMatches (http://localhost:3000/static/js/bundle.js:258:5)
    at div
    at App

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://reactjs.org/link/error-boundaries to learn more about error boundaries.
node_modules/react-dom/cjs/react-dom.development.js:20085
There was an error: TypeError: Cannot read properties of undefined (reading 'gameArray')
src/components/ButtonMatches.js:75

This is the component "ButtonMatches.js" , which has a method that calls the fetch at the URL:

import React from 'react';
import reactDom from 'react-dom';
// import { MapList } from './MapList';
import { MatchList } from './MatchList';

export class ButtonMatches extends React.Component
{

    constructor(props)
    {
        super(props);

        this.state = { totalKD: 0, totalWL: 0 };

        this.data = {  

            gameArray : [],
            categoryArray : [],
            mapArray : [],
            victoryArray : [],
            killsArray : [],
            deathsArray : [],
        };

        this.fetchHaloMatches = this.fetchHaloMatches.bind(this);
    }

    async fetchHaloMatches()  
    {
        const gamertag = document.getElementById("search-recent-matches").value; // gamertag should be the string 'giafra'.

        const url1 = 'https://cryptum.halodotapi.com/games/hmcc/stats/players/'   
        const url2 = url1 + gamertag;  // url1 + 'giafra'                                           
        const url3 = `${url2}/recent-matches?page=1`;  // '.../giafra/recent-matches?page=1

        fetch(url3, {
            "method": 'GET',
            "headers": {
                        'Content-Type': 'application/json',
                        'Cryptum-API-Version': '2.3-alpha',
                        'Authorization': 'Cryptum-Token HOePLO1QYGIbelnxcQSq9y6ZXbX6MWahQZlQq1zaXQqHXEd4mWyOZ6YgaijjdCyB'
                     }
        })
        .then(response =>      
            response.json())   
        .then(res => {   
                    
            let totalKills = 0, totalDeaths = 0, totalVictories = 0, totalLosses = 0;
            for (let i=0; i< res.data.length; i++)
            {
                
                this.data.victoryArray[i] = res.data[i].victory;
                this.data.victoryArray[i] ? totalVictories++ : totalLosses++ 

                this.data.killsArray[i] = res.data[i].stats.kills;
                totalKills += this.data.killsArray[i];

                this.data.deathsArray[i] = res.data[i].stats.deaths;
                totalDeaths += this.data.deathsArray[i];

                this.data.mapArray[i] = res.data[i].details.map.name;
                this.data.categoryArray[i] = res.data[i].details.category.name;
                this.data.gameArray[i] = res.data[i].details.engine.name;

            }

            const totalKD = (totalKills / totalDeaths), totalWL = (totalVictories / totalLosses);

            this.setState(({  .
                totalKD : totalKD,
                totalWL : totalWL 
            })); 
        })  
        .catch(error => {   
            console.log("There was an error: " + error);
        });
    }


    render()
    {
        if ((this.state.totalKD || this.state.totalWL) === 0)  
        {
            return (   
                <div>
                    <form>
                        <label htmlFor="gamertag">Xbox Live Gamertag:</label>
                        <input type="search" id="search-recent-matches" name="Gamertag" placeholder="Enter a Gamertag..."></input>
                        <button type="submit" onClick={ this.fetchHaloMatches } >Search</button>
                    </form>
                </div>
                
            );
        }

        else  
        {
            return (
                <div>
                  <div>
                    <form>
                        <label htmlFor="gamertag">Xbox Live Gamertag:</label>
                        <input type="search" id="search-recent-matches" name="Gamertag" placeholder="Enter a Gamertag..."></input>
                        <button type="submit" onClick={ this.fetchHaloMatches } >Search</button>
                    </form>
                  </div>
                  
                  <MatchList recentMatches={ this.data } />
                </div> 
            );
        }
        
    }

}

And this is the component MatchList.js:

import React from "react";
import { MatchEntry } from './MatchEntry';

export class MatchList extends React.Component  {
    constructor(props)
    {
        super(props);

    }

    render()
    {
        for (let i=0; i< 1; i++)  
        {
            return(

              <MatchEntry game={this.props.data.gameArray[i]}  
                          category={this.props.data.categoryArray[i]} 
                          map={this.props.data.mapArray[i]}
                          victory={this.props.data.victoryArray[i]} 
                          kills={this.props.data.killsArray[i]} 
                          deaths={this.props.data.deathsArray[i]} 
              />
            );
        }
    }

}

However, I believe that "Cannot read properties of undefined (reading 'gameArray')", occurred in the MatchList component, is thrown just because the conditional rendering of MatchList in the ButtonMatches component does get triggered for some reason, passing a undefined props since that props relies on the correct execution of fetch() method and consequent correct storage of the request's data. Really stuck on this one since while debugging, any breakpoint inside the fetch() method gets ignored, and after the fetch() is executed, the network request disappears from the Network tab of Chrome Developer Tools (so I can't be sure if it's really failing or not).


Solution

  • Found a solution for this. What was happening behind the scenes was that the webpage was refreshing when the "submit" button was clicked, due to the default event associated to the submission of forms. That's why my network requests "vanished" in the Network tab of Chrome Dev Tools.

    I proceeded to edit the render of the form by returning:

    <div>
        <form onSubmit={(event) => { event.preventDefault(); } }>
            <label htmlFor="gamertag">Xbox Live Gamertag:</label>
            <input type="search" id="search-recent-matches" name="Gamertag" placeholder="Enter a Gamertag..."></input>
            <button type="submit" onClick={ this.fetchHaloMatches } >Search</button>
        </form>
    </div>
    

    As you can see, I had to call the .preventDefault() method. Since window.event is now deprecated, I found out that the onSubmit attribute returns the event (class Event) associated to the submission. I could then call the .preventDefault() method on that specific event, preventing the refresh. Remember to declare the submission button as type="submit" or this won't work.