0

I'm trying to set an array into state, but it doesn't appear to be getting set. I have posted some of my code below.

What I am looking to do is, a user can upload an item to the main collection on firestore, this item will then be shown on the main list, on the main screen.

I also want users to be able to view their own items by they clicking on there own profile and they can see a list of only there items.

I have taken the approach that the item gets uploaded to the main collection on firestore, and then the doc id, and collection reference i.e "c" both get put into a doc in a sub-collection called userItems with is in that specific user's doc. I think this is the best way to do it, so I only have "1 source of truth" and not having to worry about duplicates, especially if I have to update any documents in the future.

As I have said at the top, my problem is the array is empty once I try to iterate through it in the 2nd method "queryUserItems". if someone one would be able to point out what I am doing wrong I would be very grateful, and or if someone might be able to point out a more elegant and efficient way of what I'm ultimately trying to do, that is: saving the items in a way that they can be viewed from the main list as well as the user own list, think of it a bit like how Instagram works.

Thank you for any help :)

UserProfile.js

constructor() {
    super();

    this.getUserItems = this.getUserItems.bind(this);


    this.state = {
        Name: '',
        Location: '',
        items: [],
        Keys: [],
        test: ''
    };
}

componentDidMount() {
    console.log('UserProfile');

    this.getUserItems();
    //this.queryKeys();

}


getUserItems = () => {

    const Keys = [];

    const userCollectionRef = firebase.firestore()
        .collection('a').doc('b')
        .collection('c')




    userCollectionRef.get()
        .then((querySnapshot) => {
            console.log("user doc received");

            querySnapshot.forEach(function (doc) {

                console.log('Doc.id: ', doc.id);
                console.log('Doc.Key: ', doc.data().Key);
                console.log('Doc.CollectionRef: ', doc.data().CollectionRef);

                const {Key, CollectionRef} = doc.data();

                Keys.push({
                    Key,
                    CollectionRef
                })

            }); // foreach loop end
this.queryKeys(keys);




        }).catch(function (error) {
        console.error("getUserItems => error: ", error);
    });

  //  this.setState(() =>({
    //    Keys,
      //  test: 'testString'
   // }));

    console.log("Keys inside: ", Keys);
};


queryKeys(keys) {
    const items = [];


    console.log("queryKeys Called!");
    console.log("queryKeys :", keys);
    console.log('test: ', this.test);



    keys.forEach(function(Key, CollectionRef)  {

        console.log("Key array: ", Key);
        console.log("CollectionRef array: ", CollectionRef);

        firebase.firestore
            .collection('a').doc('b')
            .collection(CollectionRef)
            .doc(Key)
            .get().then(function (doc) {

            console.log("doc received");

            const {Name, imageDownloadUrl, Location} = doc.data();

            items.push({
                key: doc.id,
                doc, // DocumentSnapshot
                Name,
                imageDownloadUrl,
                Location,
            });

        }).catch(function (error) {
            console.error("queryKeys: error: ", error);
        })

    }) // forEach end

    this.setState({
        items
    });

}

UPDATE:

I have decided to take a different approach, where I just call the queryKeys function at the end of the .then in getUserItems and just pass in the keys as a parameter. this way appears to work as it gets the array in at the right time, but now Im getting an error from firestore: firestore error

when I do this :

firebase.firestore
        .collection('a').doc('b')
        .collection('c')
        .doc('docID').get()
        .then((doc) => {

How can I get a document by id ?

Thank you

3
  • 1
    Your issue with with the asynchronous nature of your code, your setState is being called, before the loop has finished. You need to set state after the items have all finished Commented Oct 18, 2018 at 15:27
  • 1
    getUserItems and queryKeys both execute asynchronous functions. getUserItems is setting state after queryKeys is called because it fetches data from an external source. getUserItems should return a promise or you can use async/await, but it should return the Keys and then pass them to queryKeys Commented Oct 18, 2018 at 15:30
  • I tried calling the 'queryKeys' function in the 'getUserItems' function at the end of the .then after the for loop, but then I had problems calling the queryKeys function, I kept getting errors saying queryKeys is not function. even though I feel this way of doing it is better. thank you both for your comment Commented Oct 18, 2018 at 15:37

1 Answer 1

1

The modifications i've made below are super dirty, your problem i suspect is that you're calling setState in both your methods above, before your loop has executed.

You're looping synchronously, performing an asyncronous action inside the loop, exiting the loop (syncronously), and calling setState, expecting the arrays to be filled with the data fetched inside the loop, this won't happen, as you're not waiting on the AJAX requests to firebase.

What i've done below is put the calls to setState inside the resolve of the promise in each of your loops, this is not ideal (in-fact, it's a terrible idea for production), as it will update state for every item in the list, rather than once all data has been collected. You need to make that loop there are many ways of doing that. It should demonstrate the point though, and you should see data on your screen.

constructor() {
    super();

    this.getUserItems = this.getUserItems.bind(this);


    this.state = {
        Name: '',
        Location: '',
        items: [],
        Keys: [],
        test: ''
    };
}

componentDidMount() {
    console.log('UserProfile');

    this.getUserItems();
    this.queryKeys();

}


getUserItems = () => {

    const Keys = [];

    const userCollectionRef = firebase.firestore()
        .collection('a').doc('b')
        .collection('c')




    userCollectionRef.get()
        .then((querySnapshot) => {
            console.log("user doc received");

            querySnapshot.forEach(function (doc) {

                console.log('Doc.id: ', doc.id);
                console.log('Doc.Key: ', doc.data().Key);
                console.log('Doc.CollectionRef: ', doc.data().CollectionRef);

                const {Key, CollectionRef} = doc.data();

                Keys.push({
                    Key,
                    CollectionRef
                })


            });

              this.setState(() =>({
                    Keys,
                    test: 'testString'
                }));

        }).catch(function (error) {
        console.error("getUserItems => error: ", error);
    });



    console.log("Keys inside: ", Keys);
};


queryKeys() {
    const items = [];


    console.log("queryKeys Called!");
    console.log("queryKeys :", this.state.Keys);
    console.log('test: ', this.test);



    this.state.Keys.forEach(function(Key, CollectionRef)  {

        console.log("Key array: ", Key);
        console.log("CollectionRef array: ", CollectionRef);

        firebase.firestore
            .collection('a').doc('b')
            .collection(CollectionRef)
            .doc(Key)
            .get().then(function (doc) {

            console.log("doc received");

            const {Name, imageDownloadUrl, Location} = doc.data();

            items.push({
                key: doc.id,
                doc, // DocumentSnapshot
                Name,
                imageDownloadUrl,
                Location,
            });

         this.setState({
             items
         });

        }).catch(function (error) {
            console.error("queryKeys: error: ", error);
        })

    }) // forEach end



}

Below you'll see a rough example of how you could make the loop asyncronous, using async & await, unsure if Firebase API plays well with this, you will probably need to do a little reading on their docs.

queryKeys() {
    const items = [];


    console.log("queryKeys Called!");
    console.log("queryKeys :", this.state.Keys);
    console.log('test: ', this.test);



    this.state.Keys.forEach(async function(Key, CollectionRef)  {

        console.log("Key array: ", Key);
        console.log("CollectionRef array: ", CollectionRef);

        try {
            let result = await firebase.firestore
            .collection('a').doc('b')
            .collection(CollectionRef)
            .doc(Key)
            .get();


            const {Name, imageDownloadUrl, Location} = result.data();

            items.push({
                key: doc.id,
                doc, // DocumentSnapshot
                Name,
                imageDownloadUrl,
                Location,
            });
        } catch (e) {
            console.error("queryKeys: error: ", error);
        }
    }) // forEach end


    this.setState({
        items
    })

}
Sign up to request clarification or add additional context in comments.

2 Comments

thank you for your answer, but I have changed my approach, would you able show me what I'm doing, that's causing the error from firestore
You changed your approach, thus you should create a new question my friend ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.