JavaScript array not iterable - javascript

I hit a really weird issue with a simple JavaScript array last night. I am working on a React Native app powered by Firebase and getting data from the real-time database with the SDK.
Here is the code:
var app = this
this.props.fb.auth().onAuthStateChanged(function(user) {
if (user) {
app.props.fb.database().ref('/users/' + user.uid + "/timeline").once('value').then(function(snapshot) {
var posts = snapshot.val()
return posts
}).then((posts) => {
var statuses = [];
for (i in posts) {
app.props.fb.database().ref('/posts/' + posts[i]).once('value').then(function(snapshot) {
statuses.push(snapshot.val())
});
}
console.log(statuses)
})
}
});
The above code is supposed to get the data from the timeline of each user, iterate through each of the posts on the timeline and then get the data of the post from posts. It then simply pushes the data to the statuses array, which is being console logged at the end.
This is what shows up on the console.
Until the array is expanded, the console doesn't show the items in the array. Additionally, when I try to get the length of the array, it returns 0 and I also can't iterate through the items on the array.
What am I doing wrong?

If posts is an iterable I would look into using some version of q.all and then when all promises are resolved you can reliably log the result of push all those pieces into the statuses array.
some pseudo code:
if (user) {
app.props.fb.database().ref(`/users/${user.uid}/timeline`)
.once('value')
.then(snapshot => snapshot.val())
.then(posts => {
const statuses = [];
const promises = Object.keys(posts).map(key => {
return app.props.fb.database().ref(`/posts/${posts[key]}`).once('value')
.then(snapshot => statuses.push(snapshot.val()));
}
return Promises.all(promises)
})
.then(() => console.log(statuses));
}

Related

My firebase function update document takes MINUTES to execute the first time

I am developping an app to order food online. As backend service I am using firestore to store the data and files. The user can order dishes and there are limited stocks. So every time a user order a dish and create a basket I update the stock of the corresponding ordered dishes. I am using a firebase function in order to perform this action. To be honest it is the first I am creating firebase function.
Into the Basket object, there is a list of ordered Dishes with the corresponding database DishID. When the basket is created, I go through the DishID list and I update the Quantity in the firestore database. On my local emulator it works perfectly and very fast. But online it takes minutes to perform the first update. I can deal with some seconds. Even if it takes a few seconds (like for cold restart) it's okay. But sometimes it can take 3 minutes and someone else can order a dish during this time.
Here is my code:
//Update the dishes quantities when a basket is created
exports.updateDishesQuantity = functions.firestore.document('/Baskets/{documentId}').onCreate(async (snap, context) => {
try{
//Get the created basket
const originalBasket = snap.data();
originalBasket.OrderedDishes.forEach(async dish => {
const doc = await db.collection('Dishes').doc(dish.DishID);
console.log('Doc created');
return docRef = doc.get()
.then((result) =>{
console.log('DocRef created');
if(result.exists){
console.log('Result exists');
const dishAvailableOnDataBase = result.data().Available;
console.log('Data created');
const newQuantity = { Available: Math.max(dishAvailableOnDataBase - dish.Quantity, 0)};
console.log('Online doc updated');
return result.ref.set(newQuantity, { merge: true });
}else{
console.log("doc doesnt exist");
}
})
.catch(error =>{
console.log(error);
return null;
});
});
}catch(error){
console.log(error);
}
});
I have a couple of logs output to debug the outputs on the server. It's the doc.get() function that takes 2 minutes to execute as you can see on the logger below:
Firebase logger
Thanks for your help,
Thansk for your help. I just edited a little bit your code to make it work. I post my edited code. Thanks a lot, now it takes just 4 seconds to update the quantities.
Kid regards
//Update the dishes quantities when a basket is created
exports.updateDishesQuantity = functions.firestore.document('/Baskets/{documentId}').onCreate(async (snap, context) => {
try {
//Get the created basket
const originalBasket = snap.data();
const promises = [];
const quantities = [];
originalBasket.OrderedDishes.forEach(dish => {
promises.push(db.collection('Dishes').doc(dish.DishID).get());
quantities.push(dish.Quantity);
});
const docSnapshotsArray = await Promise.all(promises);
console.log("Promises", promises);
const promises1 = [];
var i = 0;
docSnapshotsArray.forEach(result => {
if (result.exists) {
const dishAvailableOnDataBase = result.data().Available;
const newQuantity = { Available: Math.max(dishAvailableOnDataBase - quantities[i], 0) };
promises1.push(result.ref.set(newQuantity, { merge: true }));
}
i++;
})
return Promise.all(promises1)
} catch (error) {
console.log(error);
return null;
}
});
You should not use async/await within a forEach() loop, see "JavaScript: async/await with forEach()" and "Using async/await with a forEach loop".
And since your code executes, in parallel, a variable number of calls to the asynchronous Firebase get() and set() methods, you should use Promise.all().
You should refactor your Cloud Function along the following lines:
//Update the dishes quantities when a basket is created
exports.updateDishesQuantity = functions.firestore.document('/Baskets/{documentId}').onCreate(async (snap, context) => {
try {
//Get the created basket
const originalBasket = snap.data();
const promises = [];
originalBasket.OrderedDishes.forEach(dish => {
promises.push(db.collection('Dishes').doc(dish.DishID).get());
});
const docSnapshotsArray = await Promise.all(promises);
const promises1 = [];
docSnapshotsArray.forEach(snap => {
if (result.exists) {
const dishAvailableOnDataBase = result.data().Available;
const newQuantity = { Available: Math.max(dishAvailableOnDataBase - dish.Quantity, 0) };
promises1.push(result.ref.set(newQuantity, { merge: true }));
}
})
return Promise.all(promises1)
} catch (error) {
console.log(error);
return null;
}
});
Note that instead of looping and calling push() you could use the map() method for a much concise code. However, for SO answers, I like the clarity brought by creating an empty array, populating it with a forEach() loop and passing it to Promise.all()...
Also note that since you are updating quantities in a basket you may need to use a Transaction.

Display user data with post data in one flatlist. Merge two Firebase firestore collections into one end result

I have a post feed combining of two firebase firestore collections.
userPosts = [] // collectionGroup() query of all users images
users = {} // the users data after for looping posts results
I'm currently getting both the posts from the post collection and the users data from the users collection after for looping the post uids.
This is working fine and shows the user object combined with the post object in the console.log() but It will not show in the flatlist when combining this end result to a state hook!
Basically, it seams to be working fine but I can't display it. I need the end result to be the post with the users data retrieved after for looping.
Again, console.log shows correct merge of data, attaching to state hook and displaying in flatlist is not working.
My code:
useEffect(() => { // showGlobal feed
if (showGlobalFeed) {
firebase.firestore()
.collectionGroup("userPosts")
.orderBy("creation", "asc")
.get()
.then((snapshot) => {
let posts = snapshot.docs.map(doc => {
const data = doc.data();
const id = doc.id;
const uid = data.uid;
return { id, uid, ...data }
})
for(let i = 0; i< posts.length; i++){
firebase.firestore()
.collection("users")
.doc(posts[i].uid)
.get()
.then((snapshot) => {
if (snapshot.exists) {
let user = snapshot.data();
user.uid = snapshot.id;
// the below cosloe.log works fine.
console.log('\nFOR LOOP COMBINED ==========> \n',[posts[i], user])
// the hook fails to display combined data correctly
setGlobalPosts([posts[i], user])
}
else {
console.log('does not exist')
}
})
}
// console.log({...posts})
})
}
}, [showGlobalFeed])
Flatlist = data={globalPosts}

Retrieve multiple users info from firebase auth using Node js

I am using Firebase authentication to store users. I have two types of users: Manager and Employee. I am storing the manager's UID in Firestore employee along with the employee's UID. The structure is shown below.
Firestore structure
Company
|
> Document's ID
|
> mng_uid: Manager's UID
> emp_uid: Employee's UID
Now I want to perform a query like "Retrieve employees' info which is under the specific manager." To do that I tried to run the below code.
module.exports = {
get_users: async (mng_uid, emp_uid) => {
return await db.collection("Company").where("manager_uid", "==", mng_uid).get().then(snaps => {
if (!snaps.empty) {
let resp = {};
let i = 0;
snaps.forEach(async (snap) => {
resp[i] = await admin.auth().getUser(emp_uid).then(userRecord => {
return userRecord;
}).catch(err => {
return err;
});
i++;
});
return resp;
}
else return "Oops! Not found.";
}).catch(() => {
return "Error in retrieving employees.";
});
}
}
Above code returns {}. I tried to debug by returning data from specific lines. I got to know that the issue is in retrieving the user's info using firebase auth function which I used in forEach loop. But it is not returning any error.
Thank you.
There are several points to be corrected in your code:
You use async/await with then() which is not recommended. Only use one of these approaches.
If I understand correctly your goal ("Retrieve employees' info which is under the specific manager"), you do not need to pass a emp_uid parameter to your function, but for each snap you need to read the value of the emp_uid field with snap.data().emp_uid
Finally, you need to use Promise.all() to execute all the asynchronous getUser() method calls in parallel.
So the following should do the trick:
module.exports = {
get_users: async (mng_uid) => {
try {
const snaps = await db
.collection('Company')
.where('manager_uid', '==', mng_uid)
.get();
if (!snaps.empty) {
const promises = [];
snaps.forEach(snap => {
promises.push(admin.auth().getUser(snap.data().emp_uid));
});
return Promise.all(promises); //This will return an Array of UserRecords
} else return 'Oops! Not found.';
} catch (error) {
//...
}
},
};

Receiving undefined when retrieving value from firebase

Where is my call wrong? The first console.log leads to the role object and the second console.log leads to undefined. When it should be the user.
componentDidMount(){
let user = fire.auth().currentUser;
let db = fire.database();
let roleRef = db.ref('/roles');
roleRef.orderByChild('user').equalTo(user.uid).once('value', (snapshot) => {
console.log(snapshot.val())
console.log(snapshot.val().user);
})
}
Result:
Firebase:
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
Your code doesn't take the list into account. The easiest way to do so is with Snapshot.forEach():
roleRef.orderByChild('user').equalTo(user.uid).once('value', (snapshot) => {
snapshof.forEach((roleSnapshot) => {
console.log(roleSnapshot.val())
console.log(roleSnapshot.val().user);
});
})

How to get the results of a query as an Object from Cloud Firestore?

I migrated from firebase Realtime Database to Cloud firestore in one of my ReactJS projects. And I am fairly new to Firestore.
Here's what I need. Consider that I have details of items stored in a collection 'Items'.
In Realtime database, I just had to do this to get the data and store it in the state:
database.ref('Items').once('value', (snapshot) => {
this.setState({items: snapshot.val()})
})
snapshot.val() returns all the objects in Items as a single object containing them. That makes it easy for me to render them in a table with Object.keys(this.state.items).map()
From what I know so far:
This is not the case with Firestore. Firestore returns an object that can only retrieve the data in the child only by iterating through each one of them.
So the best I could come up with was:
db.collection('Items').get().then(gotData)
gotData = (snapshot) => {
snapshot.forEach((item) => {
const item = {[item.id]: item.data()}
this.setState(previousState => ({
items : {...previousState.items, ...item}
}))
})
}
The problem with the above solution is that the state will get updated a number of times equal to the number of items in the collection. So I wonder if there is a better way to do it.
Almost there, you just want to put the empty object outside the iteration like so...
db.collection('Items').get().then(snap => {
const items = {}
snap.forEach(item => {
items[item.id] = item.data()
})
this.setState({items})
}
Or you could just map it, which I find easier to think about.
db
.collection('Items')
.get()
.then(collection => {
const items = collection.docs.map(item => {[item.id]: item.data()})
this.setState({ items })
})
Or you can use reduce to return an object containing all the items.
db
.collection('Items')
.get()
.then((collection) => {
const items = collection.docs.reduce((res, item) => ({...res, [item.id]: item.data()}), {});
this.setState({items})
})

Categories

Resources