React Not Updating Render After Setstate
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"