Reputation: 822
componentDidMount() {
const refi = database.ref("highscores");
// Pushing sorted data to highscoreArray.
refi.orderByChild("highscore").limitToLast(3).on("value", function(snapshot) {
sortedHighscores = [];
snapshot.forEach(function(child) {
sortedHighscores.push({
"username" : child.val().username,
"score" : child.val().highscore
});
});
sortedHighscores.sort(function(a,b) {
return a.score - b.score;
});
this.setState({highscoreArray : sortedHighscores.reverse()});
});
console.log("highscore is:", this.state.highscoreArray);
}
I am trying to get data from database and put it inside highscoreArray. Then I am trying to put it as a text in render. Some reason it is empty or null. This means that componentWillMount() happens after render()
Can someone explain or make this work? I am so frustrated.
I have changed the code as people have assisted me but still it is giving error:
undefined is not a function (evaluating 'this.setState({ highscoreArray:sortedHighscores.reverse()})')
Upvotes: 0
Views: 164
Reputation: 598728
As the others have said, data is read from Firebase asynchronously. And while the data is being read, you call setState()
with an empty array. Then when the data comes back, you add it to the array, but you don't call setState()
anymore.
The solution is to move the call to setState()
into the callback that fires when the data has been read (or changed):
refi.orderByChild("highscore").limitToLast(3).on("value", function(snapshot) {
sortedHighscores = [];
snapshot.forEach(function(child) {
sortedHighscores.push({
"username" : child.val().username,
"score" : child.val().highscore
});
});
sortedHighscores.sort(function(a,b) {
return a.score - b.score;
});
this.setState({highscoreArray : sortedHighscores.reverse()});
});
You'll note that I listen for a value
event, which ensures that I get all 3 scores in one go. I also clear the array before adding the scores, since this value
callback will be called whenever the last 3 scores change. So this means that your scores will automatically update in the UI, when you for example change them in the Firebase Database console. Try it. It's fun!
Given the answers from Andrew and Hennek, you might want to put this code in componentDidMount()
. But there too, you'll need to call setState()
from within the callback.
Upvotes: 0
Reputation: 8936
Basically you're not waiting for all the scores to be pushed before setting it to the state, if you turn componentWillMount into an async function I believe it will solve your issue:
async componentWillMount() {
sortedHighscores = [];
const refi = database.ref("highscores");
// Pushing sorted data to highscoreArray.
await refi.orderByChild("highscore").limitToLast(3).on("child_added", function(snapshot) {
sortedHighscores.push({
"username" : snapshot.val().username,
"score" : snapshot.val().highscore});
});
sortedHighscores.sort(function(a,b) {
return a.score - b.score;
});
this.setState({highscoreArray : sortedHighscores.reverse()});
console.log("highscorevvvvv", sortedHighscores);//this.state.highscoreArray) // these are EMPTY?!?!
}
Upvotes: 0
Reputation: 463
Data stored in a Firebase Realtime Database is retrieved by attaching an asynchronous listener to a database reference
According to the documentation.
So until the result is not know, the following code this.state.highscoreArray[0]["score"].toString()
will return an error. Before to display result, check if highscoreArray
contains some items.
Moreover, it is not a good practice to retrieve data. Take a look to the React component lifecycle:
componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore setting state synchronously in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method
Upvotes: 1