Search code examples
reactjstypescriptreact-hooksrendering

React array is empty when rendered if page is not reloaded manually


I have a Members.tsx page, which depending on the active variable displays the MembersList.tsx or TeamsList.tsx with this code: {active == "team" ? <TeamsList /> : <MembersList />}

Both of these pull the data from the backend server with this code (the phrase "Member" and "Player" are the same in my code):

export async function GetAllPlayersAsync() {
  const myHeaders = new Headers();
  myHeaders.append("Content-Type", "application/json");
  myHeaders.append("Authorization", getCookie('sessionId'));

  const requestOptions = {
    method: "GET",
    headers: myHeaders,
    redirect: "follow" as RequestRedirect
  };

  let players: IPlayer[] = [];
  
  fetch("http://localhost:3000/v1/players", requestOptions)
    .then((response) => response.json())
    .then((result) => {
      console.log(result) // Debug
      result.forEach((player: any) => {
        let playerData: IPlayer = {id: player.id, name: player.name, img: player.image_url, team: player.team, born: player.born};
        players.push(playerData);
      });
    })
    .catch((error) => console.error(error));

  return players;
}

// it is in export, because I import these from a different file

I call this with useEffect:

const [players, setPlayers] = React.useState<IPlayer[] | null>(null);

React.useEffect(() => {
  (async () => {
    const players = await GetAllPlayersAsync();
    setPlayers(players);
  })();
}, []);

And it works normally when I go to this page, and shows the players normally, but when I switch the active to "teams" or back to "players" the MembersList.tsx and TeamsList.tsx will render it as the array is empty, unless I relodad the page manually. I tested it by showing an element if the arra.length == 0 and it is empty as the render says but I log out the array on every useEffect, and players change and it has data in it.

Video: showing the problem

I tried updating the players data with setPlayers() Also tried changing the Navigate("/teams") on the buttons which change the url, to window.location.href = "/teams" but that isn't the smooth way of changing page as I wanted Tried setting the MembersList.tsxs key to Math.random() but that didn't help either


Solution

  • The problem lies in mixing async/await with then/catch in GetAllPlayersAsync: you don't await the fetch before returning players, which results in returning an empty list which is populated when second then is executed - but this happens after the component is rendered.

    There is more than one way to fix it. I recommend you commit to one way of handling asynchronicity: either async/await or then/catch (for consistency). This is how you can convert it to async/await:

        const response = await fetch("http://localhost:3000/v1/players", requestOptions);
        const json = await response.json();
        json.forEach((player: any) => {
          let playerData: IPlayer = {id: player.id, name: player.name, img: player.image_url, team: player.team, born: player.born};
          players.push(playerData);
        });