I have the following structure in my Firebase Firestore:
Is it possible to query just the document ids shown in the middle column of the second image? This is my current query, which seems to me the most logical way to do it. The console.log returns the success message, but the array is empty:
async function getListofPosts() {
try{
return await useFireStore
.collection('chats')
.doc(`${postId}`)
.collection('chat')
.get().then(res => {
let theMessageList = [];
res.forEach(doc => {
theMessageList.push(doc.data(), doc.id )
});
setMessageList(theMessageList);
});
}
catch {
console.log('error on getListofPosts')
}
finally {
console.log('getListofPosts worked')
}
}
Any help very much appreciated.
Cheers, Matt
Related issue - Firestore Cloud Function empty collection
It seems like your documents under collections chats and chat are empty (document IDs are shown in italics). So to solve this you shall use collectionGroup query.
return await useFireStore
.collectionGroup('chat')
.get().then(res => {
let theMessageList = [];
res.forEach(doc => {
theMessageList.push(doc.data(), doc.id )
});
setMessageList(theMessageList);
});
Before going with collectionGroup query, I recommend you to read this query and it's limitations from here - https://firebase.google.com/docs/firestore/query-data/queries#collection-group-query
Related
I'm trying to implement a chat app with infinite scroll using Firebase. The problem is that if I don't empty my messages array when a new message is added then they're duplicated. If I empty the messages array then it doesn't keep the previous messages.
Here is the code:
getAllMessages(matchId: string) {
this.chatSvc.getAllMessages(matchId)
.orderBy('createdAt', 'asc')
.limitToLast(5)
.onSnapshot((doc) => {
if (!doc.empty) {
this.messages = [];
doc.forEach((snap) => {
this.messages.push({
content: snap.data().content,
createdAt: snap.data().createdAt,
sendingUserId: snap.data().sendingUserId,
receivingUserId: snap.data().receivingUserId
});
});
} else {
this.messages = [];
}
});
}
And the chat service that returns the reference:
getAllMessages(matchId: string): firebase.firestore.CollectionReference<firebase.firestore.DocumentData> {
return firebase
.firestore()
.collection(`matches`)
.doc(`${matchId}`)
.collection('messages');
}
I'm pushing the messages from the collection in to a messages array. If I don't add 'this.messages = []' then it will duplicate messages every time a new one is added to the collection.
How do I only get the new document from firebase with onSnapshot instead of it iterating through all of the collection again? I only want the last message because I'm going to implement infinite scroll with another query that retrieves previous messages.
Any help would be greatly appreciated.
the query will always return the last 5 result whenever a new entry that matches the condition occurs, which will create the duplicates. What you can do is to listen to changes between snapshots
getAllMessages(matchId: string) {
this.chatSvc.getAllMessages(matchId)
.orderBy('createdAt', 'asc')
.limitToLast(5)
.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
// push only new documents that were added
if(change.type === 'added'){
this.messages.push(change.doc.data());
}
});
});
}
Following code inserts data into the 'doctors' collection in firestore. But I want to add the same data records into another collection named 'next' in the same firestore database simultaneously.
service.ts
create_Newdoctor(Record){
return this.firestore.collection('doctors').add(Record);
}
component.ts
CreateRecord(docForm: NgForm){
let Record = {};
Record['fullName']=this.fullName;
Record['email']=this.email;
Record['gender']=this.gender;
Record['role']="doctor";
this.DoctorService.create_Newdoctor(Record).then(res=> {
this.fullName="";
this.email="";
this.gender="";
console.log(res);
this.message = "New doctor added";
}).catch(error=>{
console.log(error);
});
}
Can you please tell me a way to do this.Thank you in advance.
const collections = ["doctors", "next"];
create_Newdoctor(Record, collections) {
const promises = collections.map(collectionName =>
this.firebase.collection(collectionName).add(Record)
);
return Promise.all(promises);
}
I currently have a query snapshot set up to let me recieve live data from my Firestore instance:
let query = db.collection('Tasks')
.where('active', '==', 1)
.where("starts", ">", +new Date())
.where('user', '==', userId)
.orderBy('starts', 'asc').limit(25);
let observer = query.onSnapshot(async snapshot => {
snapshot.docChanges().forEach(change => {
renderTask(change.doc, change.type);
});
}, err => {
console.trace(err);
});
However, when adding a new "Task" and going over the limit value in the query, ie when the query currently has 25 documents being listened to, it invokes the snapshot listener, saying that a document has been removed. Where change.type === 'removed', but in reality, no documents that were being listened to have changed.
I currently use this method to check if a user has actually deleted a "Task", where active == 0 but when I go over the limit for calling documents in the query it also deletes a random "Task".
I've tried adding an if clause to check if the document data contains active = 0, but this doesn't work and the document (even if it has been changed to active = 0, it still contains active = 1 and I assume Firestore doesn't bother with returning an updated document, as it assumes it's not going to be used? This isn't ideal as well as I would also assume that the listnener for the document would be decoupled as well if firestore believes the document has been removed.
How can I differentiate between documents which have actually been deleted, ie active === 0 and documents that are getting removed due to reaching the query limit threshold?
Many thanks in advance.
This is an interesting use case! There is no out-of-the-box solution. Even by using the oldIndex and newIndex properties, you cannot detect if the document was really deleted from the database ("hard delete") or just removed from the query.
One possible solution would be to query the document to see if it still exists, as shown below:
let observer = query.onSnapshot(
(snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === 'removed') {
const docRef = db.collection('Tasks').doc(change.doc.id);
docRef
.get()
.then(function (doc) {
if (doc.exists) {
console.log('STILL EXISTS');
//Do something...
} else {
console.log('HARD DELETED');
//Do something else...
}
})
} else {
//.....
}
});
},
(err) => {
console.trace(err);
}
);
Note that you should not use a async in query.onSnapshot(async snapshot => {...}) since the observer in not an async function.
I am just getting started with Firebase and am trying to determine how to best structure my Firestore database.
What I want is to find all documents from an 'events' collection where 'participants' (which is an array field on each event document which contains objects with keys 'displayName' and 'uid') contains at least one matching uid. The list of uids I am comparing against will be the users' friends.
So in more semantic terms, I want to find all events where at least one of the participants of that event is a 'friend', using the uid of the event participants and of the users friends.
Hope I haven't lost you. Maybe this screenshot will help.
Here is how I've designed the 'events' collection right now
Would a deep query like this doable with Firestore? Or would I need to do the filtering on client side?
EDIT - added code
// TODO: filter events to only those containing friends
// first get current users friend list
firebase.firestore().doc(`users/${this.props.currentUser.uid}`)
.get()
.then(doc => {
return doc.data().friends
})
.then(friends => { // 'friends' is array of uid's here
// query events from firestore where participants contains first friend
// note: I plan on changing this design so that it checks participants array for ALL friends rather than just first index.
// but this is just a test to get it working...
firebase.firestore().collection("events").where("participants", "array-contains", friends[0])
.get()
.then(events => {
// this is giving me ALL events rather than
// filtering by friend uid which is what I'd expect
console.log(events)
// update state with updateEvents()
//this.props.dispatch(updateEvents(events))
})
})
I am using React-Native-Firebase
"react-native": "^0.55.0",
"react-native-firebase": "^4.3.8",
Was able to figure this out by doing what #Neelavar said and then changing my code so that it chains then() within the first level collection query.
// first get current users' friend list
firebase.firestore().doc(`users/${this.props.currentUser.uid}`)
.get()
.then(doc => {
return doc.data().friends
})
// then search the participants sub collection of the event
.then(friends => {
firebase.firestore().collection('events')
.get()
.then(eventsSnapshot => {
eventsSnapshot.forEach(doc => {
const { type, date, event_author, comment } = doc.data();
let event = {
doc,
id: doc.id,
type,
event_author,
participants: [],
date,
comment,
}
firebase.firestore().collection('events').doc(doc.id).collection('participants')
.get()
.then(participantsSnapshot => {
for(let i=0; i<participantsSnapshot.size;i++) {
if(participantsSnapshot.docs[i].exists) {
// if participant uid is in friends array, add event to events array
if(friends.includes(participantsSnapshot.docs[i].data().uid)) {
// add participant to event
let { displayName, uid } = participantsSnapshot.docs[i].data();
let participant = { displayName, uid }
event['participants'].push(participant)
events.push(event)
break;
}
}
}
})
.then(() => {
console.log(events)
this.props.dispatch(updateEvents(events))
})
.catch(e => {console.error(e)})
})
})
.catch(e => {console.error(e)})
})
In other words, I'm trying to figure out what is the Firestore equivalent to this in SQL:
UPDATE table SET field = 'foo' WHERE <condition>`
Yes, I am asking how to update multiple documents, at once, but unlike the linked questions, I'm specifically asking how to do this in one shot, without reading anything into memory, because there's no need to do so when all you want is to set a flag on all documents matching a condition.
db.collection('table')
.where(...condition...)
.update({
field: 'foo',
});
is what I expected to work, CollectionReference doesn't have an .update method.
The
Transactions and Batched Writes documentation mentions transactions and batched writes. Transactions are out because "A transaction consists of any number of get() operations followed by any number of write operations" Batched writes are also not a solution because they work document-by-document.
With MongoDB, this would be
db.table.update(
{ /* where clause */ },
{ $set: { field: 'foo' } }
)
So, can Firestore update multiple documents with one query, the way SQL database or MongoDB work, i.e. without requiring a round-trip to the client for each document? If not, how can this be done efficiently?
Updating a document in Cloud Firestore requires knowings its ID. Cloud Firestore does not support the equivalent of SQL's update queries.
You will always have to do this in two steps:
Run a query with your conditions to determine the document IDs
Update the documents with individual updates, or with one or more batched writes.
Note that you only need the document ID from step 1. So you could run a query that only returns the IDs. This is not possible in the client-side SDKs, but can be done through the REST API and Admin SDKs as shown here: How to get a list of document IDs in a collection Cloud Firestore?
Frank's answer is actually a great one and does solve the issue.
But for those in a hurry maybe this snippet might help you:
const updateAllFromCollection = async (collectionName) => {
const firebase = require('firebase-admin')
const collection = firebase.firestore().collection(collectionName)
const newDocumentBody = {
message: 'hello world'
}
collection.where('message', '==', 'goodbye world').get().then(response => {
let batch = firebase.firestore().batch()
response.docs.forEach((doc) => {
const docRef = firebase.firestore().collection(collectionName).doc(doc.id)
batch.update(docRef, newDocumentBody)
})
batch.commit().then(() => {
console.log(`updated all documents inside ${collectionName}`)
})
})
}
Just change what's inside the where function that queries the data and the newDocumentBody which is what's getting changed on every document.
Also don't forget to call the function with the collection's name.
The simplest approach is this
const ORDER_ITEMS = firebase.firestore().collection('OrderItems')
ORDER_ITEMS.where('order', '==', 2)
.get()
.then(snapshots => {
if (snapshots.size > 0) {
snapshots.forEach(orderItem => {
ORDER_ITEMS.doc(orderItem.id).update({ status: 1 })
})
}
})
For Dart / Flutter user (editted from Renato Trombini Neto)
// CollectionReference collection = FirebaseFirestore.instance.collection('something');
// This collection can be a subcollection.
_updateAllFromCollection(CollectionReference collection) async {
var newDocumentBody = {"username": ''};
User firebaseUser = FirebaseAuth.instance.currentUser;
DocumentReference docRef;
var response = await collection.where('uid', isEqualTo: firebaseUser.uid).get();
var batch = FirebaseFirestore.instance.batch();
response.docs.forEach((doc) {
docRef = collection.doc(doc.id);
batch.update(docRef, newDocumentBody);
});
batch.commit().then((a) {
print('updated all documents inside Collection');
});
}
If anyone's looking for a Java solution:
public boolean bulkUpdate() {
try {
// see https://firebase.google.com/docs/firestore/quotas#writes_and_transactions
int writeBatchLimit = 500;
int totalUpdates = 0;
while (totalUpdates % writeBatchLimit == 0) {
WriteBatch writeBatch = this.firestoreDB.batch();
// the query goes here
List<QueryDocumentSnapshot> documentsInBatch =
this.firestoreDB.collection("student")
.whereEqualTo("graduated", false)
.limit(writeBatchLimit)
.get()
.get()
.getDocuments();
if (documentsInBatch.isEmpty()) {
break;
}
// what I want to change goes here
documentsInBatch.forEach(
document -> writeBatch.update(document.getReference(), "graduated", true));
writeBatch.commit().get();
totalUpdates += documentsInBatch.size();
}
System.out.println("Number of updates: " + totalUpdates);
} catch (Exception e) {
return false;
}
return true;
}
Combining the answers from Renato and David, plus async/await syntax for batch part. Also enclosing them a try/catch in case any promise fails:
const updateAllFromCollection = async (collectionName) => {
const firebase = require('firebase-admin');
const collection = firebase.firestore().collection(collectionName);
const newDocumentBody = { message: 'hello world' };
try {
const response = await collection.where('message', '==', 'goodbye world').get();
const batch = firebase.firestore().batch();
response.docs.forEach((doc) => {
batch.update(doc.ref, newDocumentBody);
});
await batch.commit(); //Done
console.log(`updated all documents inside ${collectionName}`);
} catch (err) {
console.error(err);
}
return;
}
I like some of the answers but I feel this is cleaner:
import * as admin from "firebase-admin";
const db = admin.firestore();
const updates = { status: "pending" }
await db
.collection("COLLECTION_NAME")
.where("status", "==", "open")
.get()
.then((snap) => {
let batch = db.batch();
snap.docs.forEach((doc) => {
const ref = doc.ref;
batch.update(ref, updates);
});
return batch.commit();
});
It uses batched updates and the "ref" from the doc.
If you have already gathered uids for updating collections, simply do these steps.
if(uids.length) {
for(let i = 0; i < uids.length; i++) {
await (db.collection("collectionName")
.doc(uids[i]))
.update({"fieldName": false});
};
};