Skip to content Skip to sidebar Skip to footer

React Not Updating Render After Setstate

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

Solution 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
      });
    });
  });
}

Solution 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.

Post a Comment for "React Not Updating Render After Setstate"