add collection and batch add documents with firebase - javascript

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.

Related

Better way to get document ID which was auto generated to create the doc or update an array inside the document Firestore / Firebase

I'm creating one document per user to store the id's of the users bookmarks. I'm using addDoc so the ID of the created doc is generated by firebase. I want to check if that document exists to create a new one or update the old. I need to have a document reference to update the doc and I had a horrible time trying to figure out a way to get a hold of that auto generated ID. My code works but it feels really hacky - Is there a better way to accomplish this?
//Add a value to the bookmarks array for individual user
export const addBookmarkForUser = async (userAuth, showId) => {
const bookmarkDocRef = collection(db, 'users', userAuth.uid, 'bookmarks')
const bookmarkSnapshot = await getDocs(bookmarkDocRef)
let userBookmarkDocId
if(bookmarkSnapshot){
bookmarkSnapshot.forEach((doc) => {
if(doc.id){
userBookmarkDocId = doc.id
console.log(userBookmarkDocId)
}
})
}
try {
if(!bookmarkSnapshot) {
await addDoc((collection(db, 'users', userAuth.uid, 'bookmarks'), {
favorites: [{showId: showId}],
}))
}else {
const userBookmarkRef = doc(db, 'users', userAuth.uid, 'bookmarks', userBookmarkDocId)
await updateDoc(userBookmarkRef, {
favorites: arrayUnion({showId: showId})
})}
} catch (error) {
console.log('Error creating bookmark', error.message);
}
};
I know I will only have one document in there for each user - is there a better way to find the doc.id?
Here is the firestore structure -
If you just want to check how many documents are present in a collection then you can use getCountFromServer() that loads the count without the actual document data.
import { collection, getCountFromServer } from 'firebase/firestore';
const bookmarkColRef = collection(db, 'users', userAuth.uid, 'bookmarks');
const bookmarksCount = (await getCountFromServer(bookmarksColRef)).data().count
console.log(`${bookmarksCount} documents found in ${bookmarksColRef.path}`)\
if (!bookmarksCount) {
// no document exists, create new
}
However, since you need the document ID and its data. You can run a simple query:
import { collection, getDocs } from 'firebase/firestore';
const bookmarkColRef = collection(db, 'users', userAuth.uid, 'bookmarks');
const bookmarksSnap = await getDocs(bookmarksColRef);
if (bookmarksSnap.empty()) {
// no document
} else {
const docData = bookmarksSnap.docs[0].data();
console.log(docData)
}

How to create a nested collection when creating a user in Firebase / Firestore where users can save bookmarked items

I want to be able to have a nested collection in firebase/firestore where I can save an authenticated users favorites. I was trying to create the collection when the user is created so I can just read/write to it later but I can't figure out how to create the collection. I have something like this:
//This function creates a new user. If the user already exists, no new document will be created
export const createUserDocumentFromAuth = async (
userAuth,
additionalInfo = {}
) => {
if (!userAuth) return;
const userDocRef = doc(db, 'users', userAuth.uid); //database instance, collection, identifier
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks'); //This triggers error
const userSnapshot = await getDoc(userDocRef);
if (!userSnapshot.exists()) {
//If user snapshot doesn't exist - create userDocRef
const { displayName, email } = userAuth;
const createdAt = new Date();
try {
await setDoc(userDocRef, {
displayName,
email,
createdAt,
...additionalInfo,
});
setDoc(bookmarkRef, { //Try to create a bookmarks collection here
favorites: []
})
} catch (error) {
console.log('Error creating user', error.message);
}
}
//if user data exists
return userDocRef;
};
I can create the user just fine but not another collection at the same time. I also tried just creating the collection when a signed-in user clicks on the bookmark button like this but I get a type error in both cases Uncaught (in promise) TypeError: n is undefined every time.
export const addBookmarkForUser = async (userAuth, showId) => {
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks');
try {
await setDoc(bookmarkRef, {
favorites: showId
});
}catch(error){
console.log('error creating bookmark', error.message)
}
};
I'm pretty new to Firebase / Firestore and all I want is to be able to save an item id in an array for an individual user when they click a button. If saving in an array is not ideal or there is any better way to do this, I am open to any suggestions at this point.
I was trying to create the collection when the user is created so I
can just read/write to it later but I can't figure out how to create
the collection.
A (sub)collection is only created when you create the first document in it. There is no way to materialize an empty collection without a document.
And it is normal that you get an error when using the doc() method as follows
const bookmarkRef = doc(db, 'users', userAuth.id, 'bookmarks');
because this method is used to create a DocumentReference and therefore you need to pass a path with an even number of path segments. In you case you pass 3 segments.
You could very well define the CollectionReference for the bookmarks subcollection as follows, using the collection() method and passing the 3 segments
const bookmarkRef = collection(db, 'users', userAuth.id, 'bookmarks');
but, until you add a document in it, it will not exist in the database.
Conclusion: You will automatically create the user's bookmarks subcollection the first time you create a bookmark for the user.
For example:
const bookmarksCollectionRef = collection(db, 'users', userAuth.id, 'bookmarks');
await bookmarksCollectionRef.add({ ... })

Firestore sub collection write with Modular Javascript V9

Here is my
users collection, chats is a sub collection in users
users -
-userID1
-chats -
- chatID1
chatDetails
-userID2
-chats -
- chatID2
chatDetails
I want to update a document in chats, here is my code
export const setChatNewMessageAlert = async (user_id, chat_id, bool) => {
const chatRef = doc(db, "users", user_id, "chats", chat_id);
const update = {
new_message: bool,
};
await setDoc(chatRef, update, { merge: true });
};
for some reason is giving me this ERROR
FirebaseError: Invalid document reference. Document references must have an even number of segments, but users/I7Flqla5m9OWStsoWs4PL3ncbQV2/chats has 3
even though I have four segments.

need help accessing Firestore sub-collection?

I'm a novice when it comes to coding (started teaching myself ~year ago), any help will be much appreciated, thank you in advance.
I saw that there is 3-4 other post on stack overflow on how to access Firestore's sub-collections.
I tried them all and had no luck, hence why I'm posting this as a new question.
right now I have my data set is up as: collection/document.array. And that was fine till now because I just needed to read that data from the array to draw it out in my little React project with .reduce and .map and push new data to that array on input.
this is the code I have right now for getting data from Firestore:
--- in firebase.js ----------------------------------------------------------------------
export const fire = firebase.initializeApp(config);
export const db = fire.firestore()
_________________________________________________________________________________________
--- in events-context.js ----------------------------------------------------------------
const fetchEvents = async () => {
try {
const data = await db.collection('events').get();
setEvents(data.docs.map(doc => ({ ...doc.data(), id: doc.id })));
} catch ({ message }) {
alert(`Error # fetchEvents, Error:${message}`);
}
};
But now I want to add edit and a remove feature, but in order to do that, I need to carry out my array of data into a sub-collection so each individual element from that array had its own id so that I could later target it. so it needs to be set up something like this: collection/document/sub-collection
To access a document inside a collection, you must know the document ID from another source. This can be done by managing the names inside an array of strings or maps that you can then process within your app per your design.
For example: once created, you will have a snapshot of the reference of which you can store the document id inside the parent document:
db.collection("events").doc(id).update({
payers: firebase.firestore.FieldValue.arrayUnion(paySnapshot.ref.id)
})`
Once you have this information, you can append it to the relevant document path using one of the following techniques.
db.collection("events").doc(id).collection("payers").doc(pay_id).get()
db.doc(\events/${id}/payers/${pay_id}`).get()`
I strongly advise against using .get() on a collection without limit() and where() conditions to reduce the reads that can occur.
Try this, it works for me :)
Insert data >>>
const q = query(collection(this.fire, "events"));
const querySnapshot = await getDocs(q);
const queryData = querySnapshot.docs.map((details) => ({
...details.data(),
id: details.id,
}));
console.log(queryData);
queryData.map(async (v, id) => {
await setDoc(doc(this.fire, `events/${auth}/more`, events.title), {
'title': events.title,
'uid': auth,
//your data here
})
})
Read Data >>>
const querySnapshot = await getDocs(collection(this.fire,
`/events/${auth}/more/`)); querySnapshot.forEach((doc) => { //
doc.data() is never undefined for query doc snapshots
console.log(doc.id, " => ", doc.data()); });
return querySnapshot;

Filtering data in Firebase

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();
}

Categories

Resources