1

I am using Firebase, React, Redux for my project and I want to send data from firebase to redux's global store using useEffect(). The problem I am facing is that I cannot access to methods and properties of an array even though I can use console.log(array) to get its element.

Let me briefly explain my code:

I'm assigning each group of images I get from database to its respective product and then send it to the redux global store.

My firebase structure:

products --> images

products is the root collection and images is a subcollection of products

Inside my useEffect(), I have defined 3 functions which is extractImagesFromDatabase, getOtherDataAndSendGlobalStore, and SendDataToGlobalStore. The reason why I use 2 functions to retrieve data from database is that extractImagesFromDatabase merely gets data from products/productsId/images but getOtherDataAndSendGlobalStore gets data from the products collection.

As their names imply, extractImagesFromDatabase is functioned as getting images from the database, getOtherDataAndSendGlobalStore is functioned as getting other data from the database, SendDataToGlobalStore is a function which calls both functions above.

After getting "images" with the extractImagesFromDatabase function, I push them to an array "images" which later I want to use it in the getOtherDataAndSendGlobalStore function.

After getting all the datas I want from the database using getOtherDataAndSendGlobalStore, I send these data together with the "images" array to the redux global store using an action called setProducts.


  let images = [];
  
  //load products and make it app wide
  useEffect( () => {
     function extractImagesFromDatabase() {
         db.collection("products").orderBy('timestamp', 'desc').onSnapshot(dataSnapshot => {
          // extract images and push them to images for later storage into global store
          dataSnapshot.docs.map(doc => {
          db.collection("products").doc(doc.id).collection("images").onSnapshot(imagesSnapshot => {
            images.push(imagesSnapshot.docs.map(doc => doc.data().image))    
          })  
        })
      })
    }
  
    console.log(Array.isArray(images))        // Output --> true
    console.log(images)                       // Output --> []
                                              //           > 0: (6) ["https://firebasestorage.googleapis.com/v0/b/secret…=media&token=3a900297-dbfc-4582-9d23-2039d9c46d6c", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=a465979b-9981-4ec1-a2ce-1295e3af7949", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=c9b52990-cb5c-4f99-b7c1-c1fb1644ab7f", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=f23e6056-c7f2-43d0-b45b-f3b4cd212556", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=b7e95f00-a976-4627-b83c-e6f8f2d3ec0d", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=3eda6895-4fec-48e3-b8c2-e81d666672ac"]
                                              //           > 1: (6) ["https://firebasestorage.googleapis.com/v0/b/secret…=media&token=02c4a6df-e9d2-4890-ac0c-d4357c80d4d9", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=baa5ff66-b2e9-4219-ab9b-5a01eb912656", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=75418f82-d210-4a71-bf1d-fd7f4840b901", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=e656dca8-c9b1-4a94-a2d4-e93d1d35b43a", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=66308ebe-7125-4d27-a18f-46f98cc421a9", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=2ffc3372-6b02-44db-8f42-3c1c2d9fb9d9"]
                                              //      length: 2
                                              //      __proto__: Array(0)
    console.log(images.map(image => image))   // Output --> []
    console.log(images.length)                // Output --> 0

    function getOtherDataAndSendGlobalStore() {
      images.forEach(image => {
        db.collection("products").orderBy('timestamp', 'desc').onSnapshot(dataSnapshot => {
          // push products data including images to global store
          setProducts( dataSnapshot.docs.map((doc) => ({
            product: {
              id: doc.id,
              name: doc.data().name,
              price: doc.data().price,
              description: doc.data().description,
              images: image
            }
          })
          ))
        })
      })
    }
  
   async function SendDataToGlobalStore() {
      await extractImagesFromDatabase();
      getOtherDataAndSendGlobalStore()
   }

   SendDataToGlobalStore()
  }, []);

The problem is that I can't use "forEach", "map" and even "length" for the "images" array which I defined at the top. However, when I use console.log(images) to check whether it is empty, I got 2 elements inside.

Output --> []
//           > 0: (6) ["https://firebasestorage.googleapis.com/v0/b/secret…=media&token=3a900297-dbfc-4582-9d23-2039d9c46d6c", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=a465979b-9981-4ec1-a2ce-1295e3af7949", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=c9b52990-cb5c-4f99-b7c1-c1fb1644ab7f", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=f23e6056-c7f2-43d0-b45b-f3b4cd212556", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=b7e95f00-a976-4627-b83c-e6f8f2d3ec0d", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=3eda6895-4fec-48e3-b8c2-e81d666672ac"]
//           > 1: (6) ["https://firebasestorage.googleapis.com/v0/b/secret…=media&token=02c4a6df-e9d2-4890-ac0c-d4357c80d4d9", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=baa5ff66-b2e9-4219-ab9b-5a01eb912656", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=75418f82-d210-4a71-bf1d-fd7f4840b901", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=e656dca8-c9b1-4a94-a2d4-e93d1d35b43a", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=66308ebe-7125-4d27-a18f-46f98cc421a9", "https://firebasestorage.googleapis.com/v0/b/secret…=media&token=2ffc3372-6b02-44db-8f42-3c1c2d9fb9d9"]
//      length: 2
//      __proto__: Array(0)

I have stucked in this problem for few days. Would be appreciate if someone could point out my error.

Jun Cheng
  • 65
  • 9
  • The error is that when you are console logging stuff, those functions are defined but not called yet. And you can only use await for a function that returns a promise. extractImagesFromDatabase does not return a promise. – Jkarttunen Jun 27 '21 at 10:35

1 Answers1

1

This code has a few obvious issues:

  1. this code will be run on every render, maybe tens of times
  2. await extractImagesFromDatabase(); won't await because the function extractImagesFromDatabase is not an async function. That's a syntactic error that you compiler should warn about.

How to fix this:

  1. Move to lead action to external function, even in external file. The useEffect should look like this:

    useEffect( () => { sendDataToGlobalStore(setProducts) }, [setProducts]);

  2. The sendDataToGlobalStore() should look something like this:

    function SendDataToGlobalStore(setProducts) {
      db
        .collection("products")
        .orderBy('timestamp', 'desc')
        .onSnapshot(dataSnapshot => {
          dataSnapshot
            .docs
            .map(doc => {
              db 
                .collection("products")
                .doc(doc.id)
                .collection("images")
                .onSnapshot(imagesSnapshot => {
                   imagesSnapshot.docs.map(doc => {
                     const image = doc.data().image
                     db
                       .collection("products")
                       .orderBy('timestamp', 'desc')
                       .onSnapshot(dataSnapshot => {
                         setProducts( dataSnapshot.docs.map((doc) => ({
                           product: {
                              id: doc.id,
                              name: doc.data().name,
                              price: doc.data().price,
                              description: doc.data().description,
                              images: image
                           }
                         })))
                      })
                   })
                })
            }) 
         })
      })
    }
    
  3. When you get that monstrosity of a function to work, look into making it simpler by splitting into separate functions, and learning about the async..await and promises.

Jkarttunen
  • 6,764
  • 4
  • 27
  • 29
  • Hi there, thanks for your answer, I thought that await can be used in any function. Thanks for pointing out my errors. – Jun Cheng Jun 27 '21 at 11:47
  • Can I ask something more? I expect the images stored in the global store to be an array of image links, instead of just an image link. Currently the code above only stores 1 link into the redux store. – Jun Cheng Jun 27 '21 at 11:50
  • Yep, you'd also need to rewrite the reducer to handle images arriving one by one. instead of everything at once. Because that is going to happen anyway. the docs.map callback is called asynchronously, you cannot force it to by synchronous by pushing stuff to images array. Why not store those image links in redux? – Jkarttunen Jun 27 '21 at 13:25