React setState in a map function

流过昼夜 提交于 2019-12-25 11:58:45

问题


I cannot wrap my head around the issue below.

The issue relates to the asynchronous setState dimension. Usually I use the callback, but doesn't seem appropriate here.

My goal is to create a state (that I will be able to sort) which is obtained by iterating on different states which are themselves created in a map.

The function below calls my different methods, the ones we're interested in are the 2 last ones. getUserPoints and sortArrayforUserRank.

getPlayersByUser = () => {
database
  .ref(`pools/${this.state.selectedValue}`)
  .once("value")
  .then(data => {
    for (let item in data.val()) {
      this.setState({
        users: this.state.users.concat([item])
      });
      this.setState({ [item]: data.val()[item] });
    }
  })
  .then(this.makePlayersArray)
  .then(this.getUserPoints)
  .then(this.sortArrayforUserRank);


  getUserPoints = () => {
this.state.users.map(user => {
  // Create the dynamic name of the state, 1 for each user
  let userPoints = `${user}points`;

  // initializing the state for userPoint to be at 0 for future calculation
  this.setState({ [userPoints]: 0 });

  this.state[user].map(player => {
    database
      .ref(`players/${player}`)
      .child("points")
      .once("value")
      .then(data => {
        let points = parseInt(data.val());
        this.setState(state => ({
          [userPoints]: points + state[userPoints]
        }));
      });
  });
});

The getUserPoints allow me to dynamically create the state.userPoints summing all the points from the players for each user.

Then I was expecting the sortArrayforUserRank below to use the updated state.userPoints to create my final userArrayPoints state.

sortArrayforUserRank = () => {
this.state.users.map(user => {
  let userPoints = `${user}points`;
  this.setState(state => ({
    userArrayPoints: state.userArrayPoints.concat([
      { [user]: state[userPoints] }
    ])
  }));
});

Currently the userArrayPoints gets populated with 4 objects {[user]:0} instead of the final sum of points for each user. The issue there is that sortArrayforUserRank gets called before the previous setState are done

I would have loved to use the setState callback in getUserPoints but since I'm in the player map function it will get called for each player whereas I want to handle it at the user lvl to have the final sum of points.

I tried to use componentDidUpdate, and made sur to use functionnal setState as per those articles but couldn't figure it out.

https://medium.com/@shopsifter/using-a-function-in-setstate-instead-of-an-object-1f5cfd6e55d1

https://medium.freecodecamp.org/functional-setstate-is-the-future-of-react-374f30401b6b

Your help will be glady appreciated,

thanks


回答1:


You can't do what you are trying here with setState because it is asynchronous and will conflict with the different states available on each iteration in that for () loop.

What you can do is extract the state first, manipulate it as needed, then run setState (at least in this one below)

.then(data => {
    // Pull out what you want to mess with here first
    const users = [ ...this.state.users ];
    const dataValObj = data.val();

    // Then use spread operator (or Object.assign/Array.concat)          
    this.setState({
      users: [ 
          ...users, 
          ...Object.keys(dataValObj)
      ],
      ...dataValObj
    });
})

And it seems you followed a similar pattern throughout the code. Try and apply what I've done here to the other areas that are using loops with setState inside them.




回答2:


found one way to do it using promise all below. It allows me to remove the setState from the loop and directly work on it instead of have to rely on 2 setstate. Both comments/answer helped me to process that,

  getUserPoints = () => {
this.state.users.map(user => {
  // Create the dynamic name of the state, 1 for each user
  let userPoints = `${user}points`;

  // initializing the state for userPoint to be at 0 for future calculation
  this.setState({ [userPoints]: 0 });

  let userCounter = 0;
  let promiseArrays = [];
  this.state[user].map(player => {
    let promise = database
      .ref(`players/${player}`)
      .child("points")
      .once("value")
      .then(data => {
        let points = parseInt(data.val());
        return (userCounter = userCounter + points);
      });
    promiseArrays.push(promise);
  });
  Promise.all(promiseArrays).then(() =>
    this.setState({
      userArrayPoints: this.state.userArrayPoints.concat({
        [user]: userCounter
      })
    })
  );
});

};




回答3:


Just use second argument in this.setState.

Second argument is a function that will be called after seting State.

this.setState({ name:value },() => {this.nameOfTheFunctionYouWantToRunNext() });



来源:https://stackoverflow.com/questions/47735600/react-setstate-in-a-map-function

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!