Filtering data in Firebase - javascript

I'm matching a user in a list of users as follows:
export async function getLoginData(uid) {
let loginData;
const usersRef = await database.ref('users');
const snap = await usersRef.once('value');
snap.forEach((item) => {
const itemVal = item.val();
if (itemVal.uid === uid) {
loginData = itemVal;
}
});
return loginData;
}
Style-wise, I'm not a fan of this. I'd much rather do a filter for the matching:
loginData = snap.filter((item) => item.val().uid === uid);
But the filter method is not available in the snapshot. Is there a way to write more clean, one line retrievals of data from Firebase? Or does it always have to be a forEach and a callback as I have above?

Before worrying about the style of your current filtering approach, it's probably better to consider its performance. You're downloading all data under the /users node to then filter out anything where item.val().uid <> uid. Such client-side filtering wastes your user's bandwidth.
You should instead use Firebase's built-in querying capabilities where possible. In this case it seems quite simple:
let loginData;
const usersRef = await database.ref('users');
const snap = await usersRef.orderByChild('uid').equalTo(uid).once('value');
snap.forEach((item) => {
const itemVal = item.val();
loginData = itemVal;
});
In this case you still need to loop. Since a query can potentially match multiple child nodes, the code needs to deal with this situation.
If you are certain that each user node has a unique UID, you should consider storing the user data with that UID as the key (instead of another generated key):
users
uid1
name: "user2878765"
uid2
name: "Frank van Puffelen"
Storing the user data under the key automatically ensures that the UID is unique and makes it that you can look up the user's data without requiring a query. That also means you don't need a forEach() anymore:
let loginData;
const usersRef = await database.ref('users');
const snap = await usersRef.child(uid).once('value');
const itemVal = snap.val();
loginData = itemVal;
That also makes it easier to return the correct promise from your function:
export async function getLoginData(uid) {
const snap = await database.ref('users').child(uid).once('value');
return snap.val();
}

Related

add collection and batch add documents with firebase

I have a screen on my app where a user inputs a number {x} and from this number I would like to create a collection in the programs doc and then add {x} documents to the collection.
Only one document gets added to the collection.
const handleContinue = async () => {
const batch = writeBatch(db);
const blockArray = [...Array(blockCount).keys()];
// use the program name as the ID.
const docRef = doc(db, `Users/${userStore.uid}/programs/${programName}`);
const payload = {
title: programName,
units: programUnits,
system: programSystem,
status: programStatus,
days: dayCount,
blocks: blockCount,
};
await setDoc(docRef, payload, { merge: true });
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const dRef = doc(db, `Users/${userStore.uid}/programs/${programName}`);
const cRef = collection(dRef, "blocks");
blockArray.forEach((index) => {
const insert = doc(cRef, `block_${index}`);
batch.set(insert, { name: `Block ${index}` });
});
await batch.commit();
}
};
Structure I'm expecting starting from programs doc
-programs (doc)
-- programs fields
-- blocks (collection) <-- known collection name
--- block_1 (doc)
--- block_2 (doc)
--- block_3 (doc)
...etc
block_1, block_2 etc would be the document ID.
As far as I can see in the code you're writing multiple documents, but all to the same collection: Users/${userStore.uid}/programs/${programName}/blocks.
If you want to create multiple collections, you'll need to vary one of the odd-indexed parameters in this path, like blocks_1, blocks_2, etc. Note though that this is not recommended in most scenarios, as the client-side SDKs have no way to request a list of the collections under a specific path, so it's typically best to use hard-coded collection names - or collection names that are implicitly known in some other way.
So I found that my array I was looping over wasn't what I expected, nothing to do with Firebase. Fixed it further upstream and now I get the results I was after.

Problem with request from Subcollection in Firestore

Hey I try to fix a Problem I need to get a subcollection based on a query before.
const kurzRef = collectionGroup(db, 'kurzwaffensub' );
const FirstOperation = query(kurzRef,where("kurzModell", "==", `${kurzModell}` ));
const getWaffenDaten = async () => {
const modell = await getDocs(FirstOperation);
const data = [];
for (const doc of modell.docs) {
const parentDoc = await getDoc(doc.ref.parent.parent);
const { Name, avatarPath } = parentDoc.data();
// Thats the code snippet which I have my problem with
const waffenbilderRef = collection(db, 'users', doc.data().uid, 'waffenbildersub')
let subCollectionDocs = await getDocs(waffenbilderRef)
//
data.push({
...doc.data(),
Name,
subCollectionDocs,
avatarPath
});
The documents which I get with the first operation have a String Fieldvalue of the document ID
After that I need to get the subcollection based on the Fieldvalue document ID
Which is one of 3 subcollections which you can see in the picture.
Unfortunately I get something back which I dont fully understand
As you can see I get the subCollectionDocs, but it doesn't display the Data.
You can see next to the RED marked Arrow that there are 2 Documents in the result of the subcollectionDocs. This is right but I don't know how to retrieve the data properly.
You're adding the full DocumentSnapshot from the subcollection query to your state, which is what then shows when you console.log it.
My guess is that you're expecting to just see the document's data and ID, which you can do with:
const subCollectionDocs = await getDocs(waffenbilderRef)
const subCollectionData = subCollectionDocs.docs.map((doc) => {
return { id: doc.id, ...doc.data() };
}
console.log(subCollectionData);
data.push({
...doc.data(),
Name,
subCollectionData,
avatarPath
});

how to get documents with user information (firestore)

I have collection with documents.
Structure (fields):
questions
- question
- userID
and my project uses Firebase Authentication. How to get questions with data about author of each question (avatar and name) in javascript.
You need to store every user in collection (ex. 'users' ) after register, and then make leftJoin with both questions and users collections.
something like this:
async function getQuestionsWithAuthors() {
const result = [];
const questionsSnap = await firebase.firestore().collection('questions').get();
for(const doc of questionsSnap.docs) {
const questionData = doc.data();
const userData = await firebase.firestore().collection('users').doc(questionData.userID).get();
result.push({...questionData, user: userData});
}
return result
}

Matching properties of objects in two different arrays

Background
I'm building a "Liked Stores" screen for an app I'm developing. This screen renders a flatlist which shows cards with each store a user has liked. The way I'm handling the process is that when a user likes a store, it creates a new doc in firestore which holds the id of the store and a "liked" boolean.
Problem
I've been trying to filter the totality of stores in my db and match the id's of the liked stores with the id's in the "liked" collection documents.
The data structure is:
user/uid/favorites, each doc here contains 2 fields. An id (matching to another id in the stores/ collection) and a boolean indicating that it has been liked. And then stores/'s docs contain lots of data but what matters here is a field which holds an id.
I've tried implementing the following solution, with two subscribers bringing each collection and a function which handles the filtering. But for some reason I don't quite understand I just can't get it to work.
Code is as follows:
const [stores, setStores] = useState([]);
useEffect(() => {
const subscriber = firebase.firestore()
.collection('stores/')
.onSnapshot(querySnapshot => {
const stores = [];
querySnapshot.forEach(documentSnapshot => {
stores.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setStores(stores);
});
return () => subscriber();
}, [])
const [liked, setLiked] = useState([]);
useEffect(() => {
const subscriber = firebase.firestore()
.collection('users/' + userId + '/favorites')
.onSnapshot(querySnapshot => {
const liked = [];
querySnapshot.forEach(documentSnapshot => {
liked.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
setLiked(liked);
});
return () => subscriber();
}, [])
const usefulData = [];
const handleFilter = (obj) => {
for(let i = 0; i < stores.length; i++){
if(obj.id === liked[i].storeId) {
usefulData.push(obj)
}
}
}
stores.filter(handleFilter);
And then I'm passing usefulData as the data param to the FlatList.
When printing usefulData it simply returns an empty array.
Question
Can anyone provide a solution and an explanation as to why this is not working and how to compare two arrays of objects' values properly?
Well, figured it out in the most uncomfortable and least elegant possible way so any tips or better ideas will still be fondly accepted:
const likedIds = [];
liked.forEach(o => likedIds.push(o.id));
const storeIds = [];
stores.forEach(o => storeIds.push(o.id));
const intersection = storeIds.filter(element => likedIds.includes(element));
const [data, setData] = useState([]);
const finalArray = [];
useEffect(() => {
for (let i = 0; i < intersection.length; i++){
const fetchQuery = async () => {
const storeData = await firebase.firestore()
.collection('stores/')
.doc(intersection[i])
.get()
.then(documentSnapshot => {
if (documentSnapshot.exists) {
finalArray.push(documentSnapshot.data())
}
});
setData(finalArray)
}
fetchQuery()
}
}, [])
Without changing anything else from what is posed in the question, this is what I added in order to get this working. First I get all the Id's out of each array of objects, then filter them and store them in a new array. Then we can go ahead and build a new array of objects using a handy fetchQuery async function to get all the data from the original stores collection, just that this time it only brings up the stores with ids in the intersection array.
I am perfectly aware that this is the most bodged out and disgusting possible solution, but at the very least it works. Hope someone smarter than me can get some help out of this and make my solution much better!

Firestore specific data read does not work with variable but works with static value

I am trying to query specific document from firestore database. The problem seems to be that If I add the doc(id) statically, it works but with variable it does not even tho the variable has correct and exact same value I tested statically with.
The document I am trying to retrieve is a User node/document under /users collection.
read is the function I am using to retrieve the data:
export default class GenericDB {
constructor(collectionPath) {
this.collectionPath = collectionPath
}
/**
* Read a document in the collection
* #param id
*/
async read(id) {
const result = await (await firestore())
.collection(this.collectionPath)
.doc(id)
.get()
const data = result.exists ? result.data() : null
if (isNil(data)) return null
this.convertObjectTimestampPropertiesToDate(data)
return { id, ...data }
}
}
This is my vuex action:
getUser: ({ commit }, userId) => {
return new Promise((resolve, reject) => {
//usin UsersDB() instead of Generic() because my UsersDB() has constructor with correct path to /users
new UsersDB().read(userId).then(user => {
//Empty user if userId value is from variable and not empty if I use static value
resolve(user)
})
})
}
And I do call it out:
mounted() {
if (this.id) {
//getUser function is declared inside ...mapActions('authentication', ['getUser'])
this.getUser(this.id)
}
}
Update 1: I did compare static string against my variable with logical operator and it turns out that the variable userId has space at the end. I have no clue why and where does it come.
There is no error just empty data. I can not see what can be wrong with this simple query. Any help is appreciated!
Try making connection this way instead of directly using it.
const db = firebase.firestore();
async function read(id) {
const result = await db
.collection(this.collectionPath)
.doc(id)
.get()
const data = result.exists ? result.data() : null
if (isNil(data)) return null
this.convertObjectTimestampPropertiesToDate(data)
return { id, ...data }
}
Generally, standard format we mostly use to get document is :
const db = firebase.firestore();
const result = await db
.collection("collection_name")
.doc("document_id")
.get();
I hope this helps you. Please let me know for any issues.
After you edited the question I tried passing a valid variable and I am getting response. This is giving me data of document.
//Firebase
const admin = require("firebase-admin");
let serviceAccount = require("./firebase.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount)
});
let db = admin.firestore();
//End of Firebase
id = "lWxkvqZnBxNRke4SFyJj"
async function getData(id) {
const result = await db
.collection("users")
.doc(id)
.get();
data = result.data()
console.log(data)
return data
}
getData(id)
It turned out that the userId was not exatctly the same if I compared them with logical operator. The variable version had space at the end.
So the solution was to use userId.replace(/\s/g, '')
I was facing the same issue recently. Then I figured out that my string has quotes. The following code solved the problem.
roomId.trim().replace(/['"]+/g, '')

Categories

Resources