Reducing the amount of reads in a where query - javascript

I have recently implemented firebase into my project and I have created a user collection, this collection has a document for each user and each document has about 8 fields, when my user launches the app, I am trying to pull the document that corresponds to his data, so im doing the following query:
async function getUserData() {
const _collection = collection(db, "users")
const _query = query(_collection, where("userid", "==", uniqueUserID))
const querySnapshot = await getDocs(_query)
querySnapshot.forEach((doc) => {
console.log(doc.data())
})
setLoadingStatus(false)
}
This query works and gives me the corresponding user data, but the problem is, if the user is too far down the collection, this will execute 8 reads per document until it gets to the corresponding user, I have tried to implement a cache system using a lastModified but I still need to read the document data for that field and it will end up using more or less the same amount of reads. My question is: How do I reduce the amount of read operations that get executed when im trying to compare values in the documents, I have also thought of adding an a like so a_uniqueUserID so it gets ordered alphabetically and takes the first spot of the document but it's hacky.
EDIT: Here is what my structure looks like:

I think you are misunderstanding the definition of a document and a field. When you read a document, you always get all fields out of it. The snapshot contains everything read, even if you don't use it. There is no additional cost per field, other than the storage required to hold it all. In your screenshot, you show 5 documents, and one of those documents have 8 fields.
You are probably misunderstanding the metrics in the console. When you read and write documents using the console, those are also billed as reads and writes - use of the console is not "free". What you are seeing is a combination of what your app is doing in combination with what you're doing in the console.

Related

Get all collections from a document Firebase

I'm building a fullstack todo app using firebase to store user information. My idea is that each user can create multiple lists and write multiple 'todos' in every list (eg. Have a list for today and another for tomorrow). My data is structured in this manner:
users (collection)
-> usersID (every user is a document)
-> todos Lists (collection) -> todoId (document) -> (object with the data)
I can't seem to be able to query the multiple lists a user might have (by default, I'm currently just retriving data from the 'todos' collection). Either I'm failing to see some firestore functionality or I'm not structuring my database correctly. Any advice?
You are structuring your data fine. The problem is that the Firebase Client SDK doesn't offer a function to retrieve all subcollections a document has. To solve your problem you got multiple options.
1. The cleanest but most exhaustive way
You upgrade to the Blaze Plan and use Firebase Functions with the Admin SDK. The Admin SDK offers a function called listCollections() which exactly does what you want. The disadvantage is that Firebase Functions have something called coldstart which means that when your function was idle for some minutes it takes up to 20 seconds (from my experience) to run again after you called it, which can be really frustrating. Otherwise you can run a free node.js server on something like Heroku to use the Admin SDK there to avoid coldstart (better option in my opinion). The Admin SDK is explicitly not meant to be used on the client side. Check this thread
2. List all your todo lists (easiest way)
Everytime the user creates a new list, you add that to an array in your users document like this:
// Representation of your user document
user
name: "Florian"
lists: [
"todos",
"anotherList"
]
So in that way you can just fetch your user, get the lists property from it and call all lists with a loop over the lists array. As alternative just display the lists content and let the user open it explicitly to save reads.
3. Restructuring your database (I would recommend it)
If you don't have a lot of data yet, I would recommend to restructure your database like:
users - the collection with all your users (without the todo list subcollections
lists - containing a document for every created list
- each document contains a field with `owner`
- each document contains a field with the list name
- each document owns a subcollection with todos
If you use this way you could just query the database like
const getLists = async () => {
const ref = collection(db, 'lists');
const q = query(ref, where('owner', '==', theUserYouWantToQuery);
// returns array with all list (including listname) of that user.
const lists = (await getDocs(q)).docs.map(doc => ({ ...doc.data(), doc.id }));
}
when you have all list objects from that user you can either loop over that array to get all todos in the lists or let the user only fetch one list by opening it

How to update a specific field of data only one time for each user in firesrore in firebase v9?

I am creating a sns-like web application, and trying to implement a "liking a post" function. Basically each post has data in firestore, such as the caption of the post, the image of the post, number of likes on the post and etc. What I want to do is, if a user clicks a like button on any post, the number of likes on the post clicked will be incremented by 1. However, I do not know how I can restricting each user can like only one time on each post. My current code works for incrementing the number of likes but the user can like a post as many times as the user clicks.
I am totally a beginner in firebase, react and javascript and would be really appreciated for any help. Thank you.
here is my code working for incrementing a number of likes.
const likeHandle = async () => {
const docRef = doc(db, "posts", postId);
await updateDoc(docRef, {
noLikes: noLikes + 1
});
}
You won't be able to do this at scale inside the same document where you are tracking the number of likes, because there is a max size to each document.
What you will have to do instead is store a record of each pair of user and like in its own document in a separate collection, and check to make sure that any new like requests are not already represented in that other collection. Your UI should also probably disable likes if there is a record for that pair of user and post so that the user is not presented with an option that you don't want them to take.
There is no direct way to limit how often a user can write a specific piece of data.
What you can do however is:
Add a usersWhoVoted field with UIDs to the document, or a userWhoVoted subcollection under it.
Write both the increased number of likes and the UID to the database in one operation (using a transaction/batched write if you chose a subcollection).
Use security rules that only allow the user to increment the count if they also added their UID in that operation, and only decrease the count if they removed it (if that is also a use-case you want to support).
You should probably also use the atomic increment operation, to prevent depending on client-side state that can be outdated (although your security rules should catch and reject that situation anyway).

Firestore not getting string from react native calendars

Hi there i am trying to insert in my firestore db, i have noticed that the data goes through but if i add it manually in the console the color of the id is diferent, is darker as you can see in the picture.
When i do a query only finds the ones i have inserted manually
In my Home.js i get the datestring value from react native calendars by wix
onDayPress={(day) => {
const dateString =day.dateString.toString()
setDateString(dateString); console.log(dateString) }}
// Initially selected day
And then in App.js i create my task with the dateString
const createTask = async (collection, data) => {
//adding data to a collection with automatic id
//const ref = await addDoc( collection(FSdb, FScollection ), data )
const ref = await setDoc(doc(FSdb, `usertasks/${user.uid}/events/${data.dateString}/items/${new Date().getTime()}`), data)
//console.log( ref.id )
}
I dont know what im doing wrong
I tried to replicate based on the screenshot and code provided.
See below for my replication:
The issue happens in two cases:
Deleting collection or document with sub collection and sub documents.
Adding collection and documents to an empty document. If you create directly something like /collection/document(1)/collection/document(2)`. You should create it in two steps:
create: /collection/document(1)
create: /collection/document(1)/collection/document(2)
The italicized document IDs shown in the UI actually doesn't exist but its still shown so that you can still navigate to the subcollections. Since there is no document, the document itself will not show up in query results. If you want any document to exist, you have to write some code to create it and give it some fields. If you never explicitly create a document, then it never exists.
You may also refer on this documentation for more information.

How to read last message node in firebase for js app?

I want to read last message node as shown in image below.
I am using below query to retrieve last message where suid and ids are known(as shown in img). but not working.
firebase.database().ref("Messages/"+suid).child(ids+"/chat").orderByKey().limitToLast(1);
How to retrieve last message from firebase realtime db and display it to my console? Thanks
Your existing code looks fine, so let's store that query in a variable first:
const query = firebase.database().ref("Messages/"+suid).child(ids+"/chat").orderByKey().limitToLast(1);
Now you can read the message(s) this query matches with either once() or get(). I'll use once(), since it's much older and hence you're more likely to find examples of it elsewhere:
query.once("value").then((snapshot) => {
snapshot.forEach((message) => {
console.log(message.val().message.msg);
});
})
Some things to note here:
Since a query may have multiple results, the snapshot contains a list of those results. Even if there's only a single result (like here), the snapshot is a list of one result. To loop over these results, we use snapshot.forEach().
Your msg is nested under message in each child, so we use message.val().message.msg to get at its value.

Could I get 'creationTime' when I create a new document in Firestore (instead of using 'documentRef.get()' after creation and thus avoid that reading?

Goal: get the creation time of a new Firestore document in the document creation process
First I create a new document with the corresponding data newDocumentData and a field creationTime with the creation time, as shown below:
const newDocumentRef = await collectionRef.add({
...newDocumentData,
creationTime: firebase.firestore.FieldValue.serverTimestamp()
})
Then, I need the creationTime, and I follow like this...
const document = await newDocumentRef.get()
if (document.exists) {
const documentData = document.data()
}
...to finally get my documentData.creationTime.
My question is: Is there any way to get creationTime with newDocumentRef in the first step and, therefore, avoiding the rest of the process?
Thank you!
No, it is not possible with the Client SDKs, "to get the value of creationTime in the first step". FieldValue.serverTimestamp() returns a sentinel value that tells the server to assign a server-generated timestamp in the written data.
In other words the exact value for creationTime will be calculated by the server, and if you want to get this value, you need to query the database for it.
Note that, on the other hand, with the Firestore REST API, when you create a document, you get back (i.e. through the API endpoint response) a Document object that contains, among others, a createTime field.

Categories