Get filtered data from Firestore - javascript

I have the following method for displaying 8 latest posts in the app. User will press Show more button and other 8 posts will be displayed. I am trying to add a filter to search by specific fields of the post, and expect the results to be paginated as well. The problem is that I am stuck how to use the query created to get those specific posts and if the user clicks back or cancels filter how to retrieve the latest posts again. I would appreciate if someone could explain how should I get this done.
const getPosts = async () => {
let docs;
let postsReference = firebase.firestore().collection("products").orderBy("createdAt").limit(8);
await postsReference
.get()
.then(documentSnapshot => {
docs = documentSnapshot;
lastVisible = documentSnapshot.docs[documentSnapshot.docs.length - 1];
console.log("last", lastVisible);
});
docs["docs"].forEach(doc => {
postsArray.push(doc.data());
});
postsArray.forEach(function (post) {
createPost(post);
})
}
const paginate = async () => {
let docs;
let postsReference =
firebase.firestore().collection("products").orderBy("createdAt").startAfter(lastVisible).limit(8);
console.log(postsReference);
await postsReference
.get()
.then(documentSnapshot => {
docs = documentSnapshot;
console.log(docs);
lastVisible = documentSnapshot.docs[documentSnapshot.docs.length - 1];
});
docs["docs"].forEach(doc => {
createPost(doc.data());
postsSize++;
});
}
if (showMoreButton != null) {
showMoreButton.addEventListener("click", function () {
paginate();
});
}
applyFilterButton.addEventListener("click", async function () {
....
let filterQuery = firebase
.firestore()
.collection("products")
.where("productType", "==", productTypeOption)
.where("productLocation", "==", productLocationOption);
if (productPriceOption == "high") {
filterQuery = filterQuery.orderBy("price", "desc");
} else {
filterQuery = filterQuery.orderBy("price");
}
await filterQuery
.get()
.then(function (querySnapshot) {
console.log(querySnapshot.docs);
querySnapshot.forEach(function (doc) {
// Does not print to console
console.log("Inside querySnapshot");
console.log(doc.id, " => ", doc.data());
});
});
});

Related

How i can avoid the loop on my querysnapshot.foreach?

My User has 2 teams :
Danse
Judo
On my subcollection "membersList" the team Danse had 1 friend request and Judo had none.
So I'm suppose to have just one request on my screen. But when I have 2 or more teams, the while continue to loop and the request appear with the numbers of team.
I think the problem is on my querySnaphost.forEach but on my console he return me the doc not empty (so the team danse ) and an other one with document not found.
let fetch = async () => {
firestore()
.collection("Teams")
.where("uid", "==", await AsyncStorage.getItem("userID"))
.get()
.then((querySnapshot) => {
if (querySnapshot.empty) {
console.log("no documents found");
} else {
querySnapshot.forEach((doc) => {
let Teams = doc._data;
console.log(Teams);
updateActivity((arr) => [...arr, Teams]);
console.log(Activity);
doc.ref
.collection("membersList")
.where("statut", "==", "en attente")
.get()
.then((querySnapshot) => {
if (querySnapshot.empty) {
console.log("no documents found cc");
} else {
querySnapshot.forEach((doc) => {
let members = doc._data;
console.log("aa", members);
updateMembersList((arr) => [...arr, members]);
console.log("cc", MembersList);
});
}
});
});
}
});
};
useEffect(() => {
fetch();
}, []);
Here is what is logged when fetch() is called:
{"Activity": "Danse", "Adress": "Plage", "City": "Nice", "Owner": true, "members": "3", "text": "bouh", "tokenTeam": "n5ounxsf2bq", "uid": "PTbEn2fba0QudXI8JE8RioQ9of53"}
[]
no documents found cc
You should not name your function fetch as this is a reserved global function which you should treat like undefined - don't assign anything to it. I recommend also using const over let where applicable.
Note: This answer makes use of the same strategy described in this answer.
const fetchMemberRequests = async () => {
const userTeamsQuerySnapshot = await firestore()
.collection("Teams")
.where("uid", "==", await AsyncStorage.getItem("userID"))
.get();
if (userTeamsQuerySnapshot.empty) {
console.log("User owns no teams.");
// empty Activity & MembersLists
updateActivity([]);
updateMembersList([]);
return;
}
// for each team, fetch the pending requests and return a { team, memberRequests } object
const fetchMemberRequestsPromises = userTeamsQuerySnapshot.docs
.map(async (teamDocSnapshot) => {
const teamData = teamDocSnapshot.data();
const memberRequestsQuerySnapshot = await teamDocSnapshot.ref
.collection("membersList")
.where("statut", "==", "en attente")
.get();
if (memberRequestsQuerySnapshot.empty) {
console.log(`Activity ${teamData.Activity} has no pending requests.`);
return {
team: teamData,
memberRequests: []
}
}
const memberRequestsArray = memberRequestsQuerySnapshot.docs
.map((memberRequestDocSnapshot) => memberRequestDocSnapshot.data());
console.log(`Activity ${teamData.Activity} has ${memberRequestsArray.length} pending requests.`);
return {
team: teamData,
memberRequests: memberRequestsArray
}
});
const memberRequests = await Promise.all(fetchMemberRequestsPromises);
// memberRequests is an array of { team: teamData, memberRequests: arrayOfMembers }
// which could be used to show the requests in groups
// these lines replicate your current code:
const allTeams = [];
const allMemberRequests = [];
for (const request of memberRequests) {
allTeams.push(request.team);
allMemberRequests.push(...request.memberRequests); // note: spreads array
}
console.log(`User owns ${allTeams.length} teams, with a total of ${allMemberRequests.length} pending requests.`);
// replace Activity and MembersList rather than append to them
updateActivity(allTeams);
updateMembersList(allMemberRequests);
}
You are reusing the variable you are looping in (querySnapshot and doc) inside the loop, so I think this is messing up everything. If you loop over querySnapshot, then you should not reuse querySnapshot again inside the loop.
Also, to avoid headaches and callback hells, use await instead of .then( .then( .then( .then()))). You can't use await inside of a method (.filter(), .map(), .forEach() etc) but you can use it inside a for loop :
let fetch = async () => {
const querySnapshot = await firestore()
.collection("Teams")
.where("uid", "==", await AsyncStorage.getItem("userID"))
.get();
if (querySnapshot.empty) {
console.log("no documents found");
return;
}
for( let doc of querySnapshot.docs){
let Teams = doc._data;
console.log(Teams);
updateActivity((arr) => [...arr, Teams]);
console.log(Activity);
let querySnapshot2 = await doc.ref
.collection("membersList")
.where("statut", "==", "en attente")
.get();
if (querySnapshot2.empty) {
console.log("no documents found");
continue;
}
for(let doc2 of querySnapshot2.docs){
let members = doc2._data;
console.log("aa", members);
updateMembersList( arr => [...arr, members]);
console.log("cc", MembersList);
}
}
}

Firestore cloud function to recursively update subcollection/collectionGroup

I have this cloud function:
import pLimit from "p-limit";
const syncNotificationsAvatar = async (
userId: string,
change: Change<DocumentSnapshot>
) => {
if (!change.before.get("published") || !change.after.exists) {
return;
}
const before: Profile = change.before.data() as any;
const after: Profile = change.after.data() as any;
const keysToCompare: (keyof Profile)[] = ["avatar"];
if (
arraysEqual(
keysToCompare.map((k) => before[k]),
keysToCompare.map((k) => after[k])
)
) {
return;
}
const limit = pLimit(1000);
const input = [
limit(async () => {
const notifications = await admin
.firestore()
.collectionGroup("notifications")
.where("userId", "==", userId)
.limit(1000)
.get()
await Promise.all(
chunk(notifications.docs, 500).map(
async (docs: admin.firestore.QueryDocumentSnapshot[]) => {
const batch = admin.firestore().batch();
for (const doc of docs) {
batch.update(doc.ref, {
avatar: after.avatar
});
}
await batch.commit();
}
)
);
})
];
return await Promise.all(input);
};
How can I recursively update the notifications collection but first limit the query to 1.000 documents (until there are not more documents) and then batch.update them? I'm afraid this query will timeout since collection could grow big over time.
Posting a solution I worked out, not following the context of the question though but it can easily be combined. Hope it helps someone else.
import * as admin from "firebase-admin";
const onResults = async (
query: admin.firestore.Query,
action: (batch: number, docs: admin.firestore.QueryDocumentSnapshot[]) => Promise<void>
) => {
let batch = 0;
const recursion = async (start?: admin.firestore.DocumentSnapshot) => {
const { docs, empty } = await (start == null
? query.get()
: query.startAfter(start).get());
if (empty) {
return;
}
batch++;
await action(
batch,
docs.filter((d) => d.exists)
).catch((e) => console.error(e));
await recursion(docs[docs.length - 1]);
};
await recursion();
};
const getMessages = async () => {
const query = admin
.firestore()
.collection("messages")
.where("createdAt", ">", new Date("2020-05-04T00:00:00Z"))
.limit(200);
const messages: FirebaseFirestore.DocumentData[] = [];
await onResults(query, async (batch, docs) => {
console.log(`Getting Message: ${batch * 200}`);
docs.forEach((doc) => {
messages.push(doc.data());
});
});
return messages;
};

Firebase when add/delete data, app do functions more than once

I have problems with my money app. When I add/delete data from my app (products collection), my app do function "sumPrices()" more than one. For example: When I add one product, make once, add another product, make twice, add another product make three etc. This happen in the same way with delete data.
A do something wrong in my code?
Callback.push push data do array where I unsubscribe events from firebase.
AddStatsUI add UI to my DOM.
index.js:
// delete products
const handleTableClick = e => {
console.log(e); // mouseevent
if (e.target.tagName === 'BUTTON'){
const id = e.target.parentElement.parentElement.getAttribute('data-id');
db.collection('users')
.doc(user.uid)
.collection('products')
.doc(id)
.delete()
.then(() => {
// show message
updateMssg.innerText = `Product was deleted`;
updateMssg.classList.add('act');
setTimeout(() => {
updateMssg.innerText = '';
updateMssg.classList.remove('act');
}, 3000);
productUI.delete(id);
products.sumPrices(user.uid, callbacks).then(value => {
sumStats.addStatsUI('','');
const unsubscribe = db.collection('users').doc(user.uid).get().then(snapshot => {
sumStats.addStatsUI(value[0], snapshot.data().budget);
})
callbacks.push(unsubscribe);
});
})
}
}
table.addEventListener('click', handleTableClick);
callbacks.push(() => table.removeEventListener('click', handleTableClick))
//add new products to firebase
const handleExpenseFormSubmit = e => {
e.preventDefault();
const name = expenseForm.productName.value.trim();
const price = Number(expenseForm.price.value.trim());
console.log(`Product added: ${name}, ${price}`);
const user = firebase.auth().currentUser.uid;
products.addProduct(name, price, user)
.then(() => {
products.sumPrices(user, callbacks).then(value => {
sumStats.addStatsUI('','');
const unsubscribe = db.collection('users').doc(user).onSnapshot(snapshot => {
sumStats.addStatsUI(value, snapshot.data().budget);
})
callbacks.push(unsubscribe);
});
expenseForm.reset()
})
.catch(err => console.log(err));
}
expenseForm.addEventListener('submit', handleExpenseFormSubmit);
callbacks.push(() => expenseForm.removeEventListener('submit', handleExpenseFormSubmit))
product.js:
class Product {
constructor(name, price, budget, user) {
this.products = db.collection('users');
this.budget = budget;
this.name = name;
this.price = price;
this.user = user;
}
async addProduct(name, price, user) { //dodaje produkt do firebase
const now = new Date();
const product = {
name: name,
price: price,
created_at: firebase.firestore.Timestamp.fromDate(now),
};
const response = await this.products.doc(user).collection('products').add(product);
return response;
}
getProducts(callback, user){ //download list from firebase
this.products.doc(user).collection('products')
.orderBy("created_at", "desc")
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
//udpate UI
return callback(change.doc.data(), change.doc.id);
}
});
});
}
updateBudget(budget, user){
this.budget = budget;
db.collection('users').doc(user).update({budget: budget});
// callbacks.push(unsubscribe);
}
async sumPrices(user, callbacks){
let finish = [];
const unsubscribe = this.products.doc(user).collection('products').onSnapshot(snapshot => {
let totalCount = 0;
snapshot.forEach(doc => {
totalCount += doc.data().price;
});
const a = totalCount;
console.log(a);
finish.push(a);
return finish;
})
callbacks.push(unsubscribe);
return finish;
};
};
sumStatsUI.js:
class Stats {
constructor(stats, circle, budget){
this.stats = stats;
this.circle = circle;
this.budget = budget;
}
addStatsUI(data, budget){
if(data) {
const outcome = Math.round(data * 100) / 100;
const sumAll = Math.round((budget - outcome) * 100) / 100;
this.stats.innerHTML += `
<div><span class="budget-name">Budget: </span> <span class="stat-value">${budget}$</span></div>
<div><span class="budget-name">Outcome: </span> <span class="stat-value outcome-value">${outcome}$</span></div>
<div><span class="budget-name">All: </span> <span class="stat-value last-value">${sumAll}$</span></div>
`;
const circle = Math.round(((outcome * 100) / budget) * 100) / 100;
this.circle.innerHTML += `${circle}%`;
} else {
this.stats.innerHTML = '';
this.circle.innerHTML = '';
}};
};
export default Stats;
I add console.log to sumPrices
App screenshot, when I add 2 products and try update budget
Okey, a add some improvement to my code, but still have problems with subscriptions. Now everything it's okey, but when I log out and log in functions getProducts() and updateBudget() no unsubscribe.
Code here:
index.js:
//get the products and render
const unsubscribe = products.getProducts((data, id) => {
console.log(data, id);
productUI.render(data, id);
}, user.uid);
callbacks.push(unsubscribe);
//update budget + form
const handleBudgetFormSubmit = e => {
e.preventDefault();
//update budget
const budget = Number(budgetForm.budget_value.value.trim());
sumStats.addStatsUI('', '');
products.updateBudget(budget, user.uid);
//reset form
budgetForm.reset();
const budgetCart = document.querySelector('#budget');
budgetCart.classList.remove('active');
// show message
updateMssg.innerText = `Your budget was updated to ${budget}$`;
updateMssg.classList.add('act');
setTimeout(() => {
updateMssg.innerText = '';
updateMssg.classList.remove('act');
}, 3000);
};
budgetForm.addEventListener('submit', handleBudgetFormSubmit);
callbacks.push(() =>
budgetForm.removeEventListener('submit', handleBudgetFormSubmit)
);
and else to onAuthStateChanged() -> if(user):
} else {
console.log('user logged out');
authUI('');
productUI.render('');
sumStats.addStatsUI('');
console.log('Callbacks array', callbacks);
callbacks.forEach(callback => callback());
callbacks.length = 0;
}
});
getProducts() and updateBudget():
getProducts(callback, user) {
//download list from firebase
this.products
.doc(user)
.collection('products')
.orderBy('created_at', 'desc')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === 'added') {
//udpate UI
return callback(change.doc.data(), change.doc.id);
}
});
});
}
updateBudget(budget, user) {
console.log('budget', budget, user);
const db = firebase.firestore();
// this.budget = budget;
db.collection('users')
.doc(user)
.update({ budget: budget });
}
When I log out and log in:
When I have getProducts and add product to collection, this function render (render()) product twice, but add to collection once. When I update budget this return budget but after that, return 0 (on DOM where show budget a can see "Infinity")
And one thing, when I log out, console return error:
TypeError: callback is not a function
at eval (index.js:182)
at Array.forEach (<anonymous>)
at Object.eval [as next] (index.js:182)
at eval (index.cjs.js:1226)
at eval (index.cjs.js:1336)
I think it's because getProducts and updateBudget don't return unsubscribe, but undefined.
Maybe someone have solution for this?

Firebase Cloud Function Firestore Transaction TypeError: Cannot read property 'forEach' of undefined

I want to update and delete using transaction in Firebase Cloud Function.
But getting an error like this:
My Code:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const firestore = admin.firestore();
exports.updateUserSize = functions.auth.user().onDelete((user) => {
var userDocRef = firestore.collection("users").where('uid', '==', user.uid).limit(1);
return firestore.runTransaction(function(transaction) {
return transaction.get(userDocRef).then((snapshot) => {
snapshot.forEach(userDoc => {
if (!userDoc.exists) {
throw "userDoc does not exist!";
}
console.log("user_uid: " + userDoc.data().uid + " | instanceDocId: " + userDoc.data().instance);
var instanceDocRef = firestore.collection("utils").doc(userDoc.data().instance);
return transaction.get(instanceDocRef).then((snapshot) => {
snapshot.forEach(instanceDoc => {
var newUserSize = instanceDoc.data().user_size - 1;
transaction.update(instanceDoc, { user_size: newUserSize });
});
});
});
});
}).then(function() {
console.log("Transaction successfully committed!");
}).catch(function(error) {
console.log("Transaction failed: ", error);
});
});
Any help will be highly appreciated.
I didn't try your Cloud Function but the problem most probably comes from the fact that with
var instanceDocRef = firestore.collection("utils").doc(userDoc.data().instance);
return transaction.get(instanceDocRef).then((snapshot) => {})
you get a DocumentSnapshot and not a QuerySnapshot as you get with
var userDocRef = firestore.collection("users").where('uid', '==', user.uid).limit(1);
...
return transaction.get(userDocRef).then((snapshot) => {
A DocumentSnapshot does not have a forEach() method. You have to do as follows, directly calling the data() method of the DocumentSnapshot:
var instanceDocRef = firestore.collection("utils").doc(userDoc.data().instance);
return transaction.get(instanceDocRef).then((snapshot) => {
var newUserSize = snapshot.data().user_size - 1;
...
})

Async function is returning undefined

I really need to brush up on my async await and promises. I would love some advice.
I'm making an async function call to firebase firestore. The function should return a string depending on a single input param.
The feature is for a 1-1 user chat.
The function is to create the chat/find existing chat, and return its ID.
Right now, I am getting undefined as the return value of openChat and can't work out why. The function otherwise works, apart from the return.
I have two functions. One is a React class component lifecycle method, the other my firebase async function.
Here is the class component lifecycle method:
async getChatId(userId) {
let chatPromise = new Promise((resolve, reject) => {
resolve(openChat(userId))
})
let chatId = await chatPromise
console.log('chatId', chatId) //UNDEFINED
return chatId
}
async requestChat(userId) {
let getAChat = new Promise((resolve, reject) => {
resolve(this.getChatId(userId))
})
let result = await getAChat
console.log('result', result) //UNDEFINED
}
render() {
return (<button onClick = {() => this.requestChat(userId)}>get id</button>)
}
and here is the async function:
// both my console.log calls show correctly in console
// indicating that the return value is correct (?)
export async function openChat(otherPersonId) {
const user = firebase.auth().currentUser
const userId = user.uid
firestore
.collection('users')
.doc(userId)
.get()
.then(doc => {
let chatsArr = doc.data().chats
let existsArr =
chatsArr &&
chatsArr.filter(chat => {
return chat.otherPersonId === otherPersonId
})
if (existsArr && existsArr.length >= 1) {
const theId = existsArr[0].chatId
//update the date, then return id
return firestore
.collection('chats')
.doc(theId)
.update({
date: Date.now(),
})
.then(() => {
console.log('existing chat returned', theId)
//we're done, we just need the chat id
return theId
})
} else {
//no chat, create one
//add new chat to chats collection
return firestore
.collection('chats')
.add({
userIds: {
[userId]: true,
[otherPersonId]: true
},
date: Date.now(),
})
.then(docRef => {
//add new chat to my user document
const chatInfoMine = {
chatId: docRef.id,
otherPersonId: otherPersonId,
}
//add chat info to my user doc
firestore
.collection('users')
.doc(userId)
.update({
chats: firebase.firestore.FieldValue.arrayUnion(chatInfoMine),
})
//add new chat to other chat user document
const chatInfoOther = {
chatId: docRef.id,
otherPersonId: userId,
}
firestore
.collection('users')
.doc(otherPersonId)
.update({
chats: firebase.firestore.FieldValue.arrayUnion(chatInfoOther),
})
console.log('final return new chat id', docRef.id)
return docRef.id
})
}
})
}
If you have any useful tips whatsoever, I would be forever grateful to hear them!
Expected results are a returned string. The string is correctly displayed the console.log of the async function).
Actual results are that the return value of the async function is undefined.
You do not return anything from your openChat function, so that function resolves to undefined.
You have to write:
export async function openChat(otherPersonId) {
const user = firebase.auth().currentUser
const userId = user.uid
return firestore // here you need to return the returned promise of the promise chain
.collection('users')
.doc(userId)
.get()
/* .... */
}
And those new Promise in getChatId and requestChat do not make much sense. It is sufficient to await the result of openChat(userId) or this.getChatId(userId)
async getChatId(userId) {
let chatId = await openChat(userId)
console.log('chatId', chatId) //UNDEFINED
return chatId
}
async requestChat(userId) {
let result = await this.getChatId(userId)
console.log('result', result) //UNDEFINED
}
You should await the results from your firestore calls if you want to return their values, you are already using async functions :
export async function openChat(otherPersonId) {
const user = firebase.auth().currentUser
const userId = user.uid
const doc = await firestore
.collection('users')
.doc(userId)
.get()
let chatsArr = doc.data().chats
let existsArr =
chatsArr &&
chatsArr.filter(chat => chat.otherPersonId === otherPersonId)
if (existsArr && existsArr.length >= 1) {
const theId = existsArr[0].chatId
//update the date, then return id
await firestore
.collection('chats')
.doc(theId)
.update({
date: Date.now(),
})
return theId
} else {
const docRef = await firestore
.collection('chats')
.add({
userIds: { [userId]: true, [otherPersonId]: true },
date: Date.now(),
})
const chatInfoMine = {
chatId: docRef.id,
otherPersonId: otherPersonId,
}
//add chat info to my user doc
firestore
.collection('users')
.doc(userId)
.update({
chats: firebase.firestore.FieldValue.arrayUnion(chatInfoMine),
})
//add new chat to other chat user document
const chatInfoOther = {
chatId: docRef.id,
otherPersonId: userId,
}
firestore
.collection('users')
.doc(otherPersonId)
.update({
chats: firebase.firestore.FieldValue.arrayUnion(chatInfoOther),
})
console.log('final return new chat id', docRef.id)
return docRef.id
}
}
You should also directly await your calls to the function :
async getChatId(userId) {
let chatId = await openChat(userId)
console.log('chatId', chatId) //UNDEFINED
return chatId
}
async requestChat(userId) {
let result = await this.getChatId(userId)
console.log('result', result) //UNDEFINED
}

Categories