I'm essentially brand new to React, so please bear with me if there are any strange or obvious mistakes that I am missing.
I am looking to create a small little project with the National Hockey League statistics API and unfortunately have run into an issue pretty early on.
Essentially, what this code is trying to do right now is to fetch all of the current team ID's through https://statsapi.web.nhl.com/api/v1/teams, and then loop through each team's response and use it's unique ID to store every player ID currently playing in the league https://statsapi.web.nhl.com/api/v1/teams/ID/roster.
The API requests seem to be working just fine, however I'm running into an issue where the check for loading seems to be functioning improperly. If I say, for example
{loading ? <p>Loading...</p> : <p>{playerList[0].jerseyNumber}</p>}
I am given a "Cannot read properties of undefined" error message.
Is the way I attempted to signify loading incorrect? Is there a better way to be certain that the API requests are 100% finished before accessing the data?
Here is my code:
function App() {
const [playerList, setPlayerList] = useState([]);
const [loading, setLoading] = useState(true);
useEffect (() => {
fetch("https://statsapi.web.nhl.com/api/v1/teams")
.then((teamsResponse) => teamsResponse.json())
.then((teamsData) => {
const teams = teamsData.teams;
teams.forEach((team) => {
fetch("https://statsapi.web.nhl.com/api/v1/teams/" + team.id + "/roster")
.then((teamResponse) => teamResponse.json())
.then((teamData) => {
const roster = teamData.roster
roster.forEach((player) => {
setPlayerList((oldList) => [...oldList, player]);
});
});
});
});
setLoading(false);
}, []);
return (
<div>
{loading ? <p>Loading...</p> : playerList && <p>{playerList[0].jerseyNumber}</p>}
</div>
);
}
You're calling setLoading(false) outside of the fetch call. That is going to invoke the fetch and call setLoading(false) without waiting for the promise to resolve.
If you want the loading indicator to be set to false only after all of the roster fetch calls then you'll need to use Promise.all with a then. where you set the loading indicator to false, such as:
const teams = teamsData.teams;
const rosterRequests = teams.map(team => {
fetch("https://statsapi.web.nhl.com/api/v1/teams/" + team.id + "/roster")
.then(teamResponse => teamResponse.json())
.then(teamData => {
// Update roster state
});
// This will be executed once all the team roster requests have completed
Promise.all(rosterRequests).then(() => setLoading(false));
You can read more about Promise.all here.
You are setting loading to false before the data is loaded. Try putting setLoading(false); into
.then((teamData) => {
const roster = teamData.roster
roster.forEach((player) => {
setPlayerList((oldList) => [...oldList, player]);
});
setLoading(false); <---
});
setLoading should be set to false when all promises are settled since there are nested requests, I have updated and tested the solution
useEffect(() => {
fetch("https://statsapi.web.nhl.com/api/v1/teams")
.then((teamsResponse) => teamsResponse.json())
.then((teamsData) => {
const teams = teamsData.teams;
let arr = [];
const allRosterData = teams.map(async (team) => {
const rosterData = await fetch(
"https://statsapi.web.nhl.com/api/v1/teams/" + team.id + "/roster"
)
.then((teamResponse) => teamResponse.json())
.then((teamData) => {
const dt = [];
const roster = teamData.roster;
roster.forEach((player) => {
dt.push(player);
});
return dt;
});
arr = [...arr, ...rosterData];
return rosterData;
});
Promise.all(allRosterData)
.then(() => {
//Change state here when all promises are settled
setPlayerList(arr);
setLoading(false);
})
.catch((err) => {
console.error(err);
});
});
}, []);