1

I need to get data that will be used for the page that I'm rendering. I'm currently getting the data in a useEffect hook. I don't think all the data has been loaded before the data is being used in the render. It's giving me an error "property lastName of undefined" when I try to use it in the Chip label.

I'm not sure where or how I should be handling the collection of the data since it's going to be used all throughout the page being rendered. Should I collect the data outside the App function?

const App = (props) => {
  const [teams] = useState(["3800", "0200", "0325", "0610", "0750", "0810"]);
  const [players, setPlayers] = useState([]);

  useEffect(() => {
    teams.forEach(teamId => {
      axios.defaults.headers.common['Authorization'] = authKey;

      axios.get(endPoints.roster + teamId)
        .then((response) => {
          let teamPlayers = response.data.teamPlayers;

          teamPlayers.forEach(newPlayer => {
            setPlayers(players => [...players, newPlayer]);
          })
        })
        .catch((error) => {
          console.log(error);
        })
    });
  }, []);


  let numPlayersNode =
    <Chip
      variant="outlined"
      size="small"
      label={players[1].lastName}
    />

  return (...
sbaden
  • 555
  • 3
  • 16
  • 30

3 Answers3

1

You should always create a null check or loading before rendering stuff. because initially that key does not exists. For example

<Chip
  variant="outlined"
  size="small"
  label={players.length > 0 && players[1].lastName}
/>

this is an example of a null check

For loading create a loading state.

1

You iterate over a teamPlayers array and add them one at a time, updating state each time, but players is always the same so you don't actually add them to state other than the last newPlayer.

Convert

teamPlayers.forEach(newPlayer => {
  setPlayers(players => [...players, newPlayer]);
});

to

setPlayers(prevPlayers => [...prevPlayers, ...teamPlayers]);

Adds all new players to the previous list of players using a functional state update.

You also have an initial state of an empty array ([]), so on the first render you won't have any data to access. You can use a truthy check (or guard pattern) to protect against access ... of undefined... errors.

let numPlayersNode =
  players[1] ? <Chip
    variant="outlined"
    size="small"
    label={players[1].lastName}
  /> : null
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
  • That worked perfectly - Thank you. I will look up functional state update to try to understand better. The truthy check makes perfect sense now. – sbaden Mar 25 '20 at 18:10
  • 1
    @sbaden Here is a [codesandbox demo](https://codesandbox.io/embed/react-bad-and-good-state-updates-2rtbk?fontsize=14&hidenavigation=1&module=%2Fsrc%2FApp.js&theme=dark) I've created to help illustrate the difference between regular and functional state updates. – Drew Reese Mar 25 '20 at 18:12
  • I see how the truthy check works but say I wanted to wait until all of the data has loaded and I don't know how much data there is. How would I get the code to wait? – sbaden Mar 25 '20 at 18:51
  • 1
    @sbaden Use the array length, i.e. `players.length ? /* render players.map(... */ : /* render null or some useful text, maybe a loading spinner */`. (*Apologies, syntax is hard to read in comments*) Check out [react conditional rendering](https://reactjs.org/docs/conditional-rendering.html) – Drew Reese Mar 25 '20 at 18:53
  • I tried using this code in a Typography component {players.length ? players[players.length - 1].lastName : null} and it just keeps updating the lastName value – sbaden Mar 25 '20 at 19:48
  • @sbaden Sounds like a new/different issue, please either update your question here (not preferred) or open a new issue (preferred). Feel free to drop a link to it here and I can take a look when I have a chance. – Drew Reese Mar 25 '20 at 19:52
1

When functional component is rendered first, useEffect is executed only after function is returned.

and then, if the state is changed inside of useEffect1, the component will be rendered again. Here is a example

import React, {useEffect, useState} from 'react'

const A = () => {
    const [list, setList] = useState([]);
    useEffect(() => {
        console.log('useEffect');
        setList([{a : 1}, {a : 2}]);
    }, []);

    return (() => {
        console.log('return')
        return (
            <div>
                {list[0]?.a}
            </div>
        )
    })()
}

export default A;

if this component is rendered, what happen on the console?

enter image description here

As you can see, the component is rendered before the state is initialized.

In your case, error is happened because players[1] is undefined at first render.

the simple way to fix error, just add null check or optional chaining like players[1]?.lastName.

Minsik Park
  • 557
  • 3
  • 9
  • 22