0

This is part of a previous post: What's the React best practice for getting data that will be used for page render?

I'm adding objects to an array with useState([]). Each object has a firstName and a lastName value. I want to add up all of the length of all the first and last names to get a total character count so that I can then get the average name length for each.

What I have currently is giving me the wrong values: firstName (100) lastName (106)

The values should be 6 & 7

  const [players, setPlayers] = useState([]);
  const [firstNameCount, setFirstNameCount] = useState(0);
  const [lastNameCount, setLastNameCount] = useState(0);

  useEffect(() => {
    setPlayers([]);

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

      axios.get(endPoints.roster + teamId)
        .then((response) => {
          let teamPlayers = response.data.teamPlayers;
          console.log(response.data.teamPlayers)
          setPlayers(prevPlayers => [...prevPlayers, ...teamPlayers]);
        })
        .catch((error) => {
          console.log(error);
        })
    });
  }, [teams]);

  useEffect(() => {
     players.forEach(player => {
            setFirstNameCount(prevCount => prevCount + player.firstName.length);
            setLastNameCount(prevCount => prevCount + player.lastName.length);
          })
  }, [players]);

If I change the code to this

useEffect(() => {
    setPlayers([]);

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

      axios.get(endPoints.roster + teamId)
        .then((response) => {
          let teamPlayers = response.data.teamPlayers;
          console.log(response.data.teamPlayers)
          setPlayers(prevPlayers => [...prevPlayers, ...teamPlayers]);

          teamPlayers.forEach(player => {
            setFirstNameCount(prevCount => prevCount + player.firstName.length);
            setLastNameCount(prevCount => prevCount + player.lastName.length);
          })
        })
        .catch((error) => {
          console.log(error);
        })
    });
  }, [teams]);

It slows down the render considerably and I get: firstName (7), lastName (7), which is still wrong.

sbaden
  • 555
  • 3
  • 16
  • 30

2 Answers2

1

I'd recommend you move your team/player loading out of your component and into a store, separate from the component, but in any case, why put the lengths in state? Why not just compute it from the player state when you need it?

const sum = values => values.reduce((r, v) => r + v, 0);
const avg = values => sum(values) / values.length;

const avgFirst = avg(players.map(p => p.firstName.length));
const avgLast = avg(players.map(p => p.lastName.length));

I realize that this would cause it to be recomputed on every render, but unless your list of players is massive I would expect this to be reasonably performant.

ray
  • 26,557
  • 5
  • 28
  • 27
  • I don't know what a store is... Your code is a lot more elegant than what I had but when I add the code I get the same numbers I got before. First name 7 & last name 7. I have a vanilla javascript version of this that is giving me 6 & 7 which is more believable. I'm also calculating the most common occurrence and am getting 6 & 6. It should be 4 & 6. – sbaden Apr 10 '20 at 19:22
  • Do your player names have leading or trailing whitespace? Are some players missing one of the fields? It's impossible to say what the correct values are without seeing the input. Show us the data. – ray Apr 10 '20 at 19:37
  • Here is a sample of the data for 1 team: https://codesandbox.io/s/wild-darkness-5qrp9?file=/src/index.js – sbaden Apr 10 '20 at 20:23
  • @sbaden I doodled up a rudimentary [store implementation demo](https://codesandbox.io/s/magical-robinson-wzxbl?file=/src/App.js) if you want to take a look. As for the numbers being a off, could it be a rounding error? Are you rounding the average? Flooring it? Truncating it? – ray Apr 10 '20 at 20:48
  • Can you join [this codesandbox session](https://codesandbox.io/live/gJQNn9)? – ray Apr 10 '20 at 21:00
  • Thank you. I'll check it out. I'm using Math.ceil(value) but I do that in my vanilla javascript version too. – sbaden Apr 10 '20 at 21:02
  • Here is a codesandbox where I've recreated the issue... https://codesandbox.io/s/elated-kirch-9dsru?file=/src/App.js – sbaden Apr 13 '20 at 21:38
  • @sbaden look at [this fiddle and its console output](https://jsfiddle.net/rayhatfield/nd0L5y7o/19/). – ray Apr 13 '20 at 23:46
  • Looks like the data in the table supports the numbers I guess but how do you explain your first vanilla js version that had different numbers? Why are we getting different numbers from javascript and react? – sbaden Apr 14 '20 at 00:20
  • Maybe the vanilla js was broken? – ray Apr 14 '20 at 00:26
0

Well instead of setting state for every increment what you could do is add up all the lengths of firstnames and lastnames and then set the state like this:

useEffect(() => {
    setPlayers([]);

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

      axios.get(endPoints.roster + teamId)
        .then((response) => {
          let teamPlayers = response.data.teamPlayers;
          console.log(response.data.teamPlayers)
          setPlayers(prevPlayers => [...prevPlayers, ...teamPlayers]);

          let firstNameCount = 0;
          let lastNameCount = 0;

          teamPlayers.forEach(player => {
            firstNameCount += player.firstName.length;
            lastNameCount += player.lastName.length;
          })
          setFirstNameCount(prevCount => prevCount + firstNameCount);
          setLastNameCount(prevCount => prevCount + lastNameCount);
        })
        .catch((error) => {
          console.log(error);
        })
    });
  }, [teams]);

Let me know if it helps :)

rzwnahmd
  • 1,072
  • 4
  • 8