React Not Updating Render After SetState

匆匆过客 提交于 2021-02-19 05:38:40

问题


I've played around with the code for a while now. I'm grabbing data from Firebase and populating a list of objects from that data onto this UserProfile page. I just want it to work so that when I go on the profile page, a list of their items is listed there.

The problem is that with this version of the code I have to click on the profile link twice for the items to show up, but the display name shows up fine. I know setState is asynchronous. I've tried setting the state in a callback in setState. I've tried checking if the snapshot exists beforehand. I've tried componentDidMount and componentDidUpdate. None of these things have helped, I just end up with this.state.items.map can't be called or newState is some empty, weird thing. I don't know where I'm going wrong now.

When I console debug this, it looks like setState is being called when nothing has been fetched from Firebase so far and never gets called later.

Maybe something is wrong with how I set newState because when I console log newState is empty when I'm trying to first set it. I just don't know when to set this at the appropriate time for the first render.

class UserProfile extends Component {
  constructor(props){
    super(props)
    this.state = {
      redirect: false,
      userId: this.props.match.params.id,
      displayName: "",
      items: []
    }
    this.userRef = firebaseDB.database().ref(`/Users/${this.state.userId}`)
    this.usersItemsRef = firebaseDB.database().ref(`/Users/${this.state.userId}/items`)
  }

  componentWillMount() {
    this.userRef.on('value', snapshot => {
      this.setState({
        displayName: snapshot.val().displayName
      });
    });
    this.usersItemsRef.on('value', snapshot => {
      let usersItems = snapshot.val();
      let newState = [];
      for (let userItem in usersItems) {
        var itemRef = firebaseDB.database().ref(`/items/${userItem}`)
        itemRef.once('value', snapshot => {
          var item = snapshot.val()
          newState.push({
            id: itemRef.key,
            title: item.title
          });
        })
      }
      this.setState({
        items: newState
      })
    });
  }

  componentWillUnmount() {
    this.userRef.off();
    this.usersItemsRef.off();
  }

  render() {
    return (
        <div style={{marginTop: "100px"}}>
          <h1>{this.state.displayName}</h1>
          <Link className="pt-button" aria-label="Log Out" to={"/submit_item/"+this.state.userId} >Submit an item</Link>
          <section className='display-itemss'>
              <div className="wrapper">
                <ul>
                  {this.state.items.map((item) => {
                    return (
                      <li key={item.id}>
                        <h3>{item.title}</h3>
                      </li>
                    )
                  })}
                </ul>
              </div>
          </section>
        </div>
      );
  }
}

回答1:


Data is loaded from Firebase asynchronously, since it may take an undetermined amount of time. When the data is available, Firebase calls the callback function you passed in. But by that time your call to setState() has long finished.

The easiest way to see this is to add a few log statements to your code:

componentWillMount() {
  this.userRef.on('value', snapshot => {
    this.setState({
      displayName: snapshot.val().displayName
    });
  });
  this.usersItemsRef.on('value', snapshot => {
    let usersItems = snapshot.val();
    let newState = [];
    console.log("Before attaching once listeners");
    for (let userItem in usersItems) {
      var itemRef = firebaseDB.database().ref(`/items/${userItem}`)
      itemRef.once('value', snapshot => {
        console.log("In once listener callback");
        var item = snapshot.val()
        newState.push({
          id: itemRef.key,
          title: item.title
        });
      })
    }
    console.log("After attaching once listeners");
    this.setState({
      items: newState
    })
  });
}

The output from this logging will be:

Before attaching once listeners

After attaching once listeners

In once listener callback

In once listener callback

...

This is probably not the order you expected. But it explains perfectly why your setState() doesn't update the UI: the data hasn't been loaded yet.

The solution is to call setState() when the data has been loaded. You do this by moving it **into* the callback:

componentWillMount() {
  this.userRef.on('value', snapshot => {
    this.setState({
      displayName: snapshot.val().displayName
    });
  });
  this.usersItemsRef.on('value', snapshot => {
    let usersItems = snapshot.val();
    let newState = [];
    for (let userItem in usersItems) {
      var itemRef = firebaseDB.database().ref(`/items/${userItem}`)
      itemRef.once('value', snapshot => {
        var item = snapshot.val()
        newState.push({
          id: itemRef.key,
          title: item.title
        });
        this.setState({
          items: newState
        })
      })
    }
  });
}

This will call setState() for every item that is loaded. Usually React is pretty good with handling such incremental updates. But just in case it causes flicker, you can also wait for all items to be loaded by using Promise.all():

componentWillMount() {
  this.userRef.on('value', snapshot => {
    this.setState({
      displayName: snapshot.val().displayName
    });
  });
  this.usersItemsRef.on('value', snapshot => {
    let usersItems = snapshot.val();
    let newState = [];
    let promises = [];
    for (let userItem in usersItems) {
      var itemRef = firebaseDB.database().ref(`/items/${userItem}`)
      promises.push(itemRef.once('value'));
    }
    Promise.all(promises).then((snapshots) => {
      snapshots.forEach((snapshot) => {
        var item = snapshot.val()
        newState.push({
          id: itemRef.key,
          title: item.title
        });
      });
      this.setState({
        items: newState
      });
    });
  });
}



回答2:


I have not used FirebaseDB before but my guess is that it is asynchronous and returns a Promise. Therefore when use .then or await when calling the database to retrieve the reference.



来源:https://stackoverflow.com/questions/48655464/react-not-updating-render-after-setstate

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