0

Someone please help. I have combed all of the doc's, examined all similar questions and really cannot see what I am missing.

I am new to react and trying to map over documents returned from an asynchronous call from a firebase db.

Here is the code:

class Posts extends React.Component {
constructor(props) {
    super(props);
    this.state = {
        data: [],
    };
}
componentDidMount() {
    let posts = [];
    db.collection("Posts").get().then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            posts.push({
                data: doc.data(),
                id: doc.id
            });
        });
    });
    this.setState({
        data: posts
    });
}
renderPosts() {
    console.log(this.state.data);
    return this.state.data.map((post, index) => {
        console.log(post);
        console.log(index);
        return (
            <div>
                {index}
            </div>
        )
    })
}
render() {
    return(
        <div>
            {this.renderPosts()}
        </div>
    );
}
}

I am sure it is something super simple but I am pulling my hair out. If I examine my state I can see the data. console.log(this.state.data); inside renderPosts even displays exactly what I need to map over, but everything inside the map callback doesn't return and I end up with a single empty Any help would be greatly appreciated.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
nickkennedy
  • 13
  • 2
  • 6
  • 1
    I assume getting data from Firebase is async right? So that means `posts.push` in the `then` of the request promise is running *after* setState. – Andrew Li Aug 06 '18 at 03:58
  • Instead, set stare inside the then callback, and instead of forEaching and pushing, use `map`. Make sure to use the right `this` by converting the callback to an arrow function – Andrew Li Aug 06 '18 at 04:00
  • Yes it's asynchronous. – nickkennedy Aug 06 '18 at 04:01

1 Answers1

2

As Li357 commented, the setState call needs to happen inside the get() callback:

db.collection("Posts").get().then(function(querySnapshot) {
    querySnapshot.forEach(function(doc) {
        posts.push({
            data: doc.data(),
            id: doc.id
        });
    });
    this.setState({
        data: posts
    });
});

The reason for this is that data is loaded from Firestore asynchronously, and by the time you were calling setState the posts variable was still empty.

The easiest way to understand this type of asynchronous loading is with a few log lines:

console.log("Before starting to get documents");
db.collection("Posts").get().then(function(querySnapshot) {
    console.log("Got documents");
});
console.log("After starting to get documents");

When you run this, the output is:

Before starting to get documents

After starting to get documents

Got documents

That is probably not the order you expected the output in. But it is the expected behavior: instead of blocking the code while the data is loaded (which would freeze the browser), the code after the get().then() block immediately executes. And then when the data is loaded, the code in the then callback is executed. That's why all code that needs the documents from the database needs to be inside the then callback.

Community
  • 1
  • 1
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Thanks Frank - I am confused then as to why in my example above when I log the state inside renderPosts() I have the data I need. If my console.log function inside of renderPosts() runs in the place of your "After starting to get the documents" in your explaination then shouldn't it be blank since state hasn't been updated yet? – nickkennedy Aug 06 '18 at 04:30
  • Most likely it's an artifact of how Chrome logs objects: they are "live" and not snapshot. Try logging `console.log(this.state.data.length);` and I expect that is to still be `0`. – Frank van Puffelen Aug 06 '18 at 04:32
  • Ah yes - it is 0 – nickkennedy Aug 06 '18 at 04:37
  • Thanks for the help, can't upvote b/c I don't have enough points - really appreciate the help – nickkennedy Aug 06 '18 at 04:40
  • @Li357 the `QuerySnapshot` does not have a `map` method: https://firebase.google.com/docs/reference/js/firebase.firestore.QuerySnapshot – Frank van Puffelen Aug 06 '18 at 14:13