We are implementing firestore in a chat. At first render we send a request to fetch 10 documents from the chat. At the same time to listen the realtime changes in these documents. querySnapshot.docChanges() is applied till the last fetched message. When user scroll onEndReached function is triggered and request is sent to get documents at the previous fetched last message time. Again to listen the changes in realtime to all the fetched documents. Again querySnapshot.docChanges() is applied to the last message time fetched with loadMore function.
It works fine as of functionality. But the issue came when at first we get 10 documents querySnapshot.docChanges() listen to the existing 10 documents as “added”. Now when loadMore is called now again it listen to the existing all documents as “added”. So If we fetched 200 documents it first listen to the 200 documents as added and if there is any change in any of that document. It listen as “modified”.
So we fetched 10 then 15 then 15 then again 15 and soo on. These are shown to user. But the issue is on each loadMore querySnapshot.docChanges() listen to the documents like 0 to 10 then 0 to 25 then 0 to 40. So the time came. We fetched 200 messages and its listened again and again. 0 to 185 and then 0 to 200.
First fetch code
useEffect(() => {
if (!chatId) {
cloudFunction('createDirectChat', {recipient: uid}).then(res => {
console.log(res.data.chatId);
setChatId(res.data.chatId);
});
} else {
firestore()
.collection('chats/' + chatId + '/messages')
.orderBy('sentAt', 'desc')
.limit(10)
.get()
.then(querySnapshot => {
const documents = [];
querySnapshot.forEach(documentSnapshot => {
let obj = {
id: documentSnapshot.id,
...documentSnapshot.data(),
};
documents.push(obj);
});
/////
if (documents && documents.length > 2) {
firstBatchLastMessageTime.current = documents[1].sentAt;
} else if (documents && documents.length == 1) {
firstBatchLastMessageTime.current = documents[0].sentAt;
}
//// checking time to add snapshot listener till that last fetched message time.
if (documents && documents.length > 0) {
firstBatchLastMessageTime.current =
documents[documents.length - 1].sentAt;
setLastMessageFetchedTime(documents[documents.length - 1].sentAt);
} else {
/*
as there is no chat
so we have to listen every change in chat collection
instead of fixing for few documents.
Its used below to avoiding endAt filter.
*/
setIsNewChat(true);
let d = new Date();
seconds = d.getTime() / 1000;
firstBatchLastMessageTime.current = {seconds};
setLastMessageFetchedTime({seconds});
}
setMessages([...documents]);
setLoading(false);
});
}
}, [chatId, setChatId, setLoading, setMessages, uid]);
loadMore Code
async function loadMore() {
let startAfterTime;
if (messages.length > 5) {
startAfterTime = messages[messages.length - 1].sentAt;
let docs = await getMoreMessages(chatId, startAfterTime);
if (docs && docs.length > 0) {
setLastMessageFetchedTime(docs[docs.length - 1].sentAt);
}
setMessages([...messages, ...docs]);
}
}
That causes a big freeze and lagg in UI. It’s of no use. Because if new document is created we want to listen that and if any other document is changed/removed we want to listen that only. Listening to already existing documents in useless. It’s not a bug but is marked in firebase documentation that it listen to all the previous documents as added.
I avoided to process the documents that were already available by checking the time of the last message I get in the first fetched. As there is not going to be any document created before the message I already fetched. But till that condition it render the loop. That causes a big lagg/freeze in UI.
any alternate or change in this scenario will be much appreciated.
This is the code to listen to the changes.:
useEffect(() => {
if (
chatId &&
chatId !== '' &&
chatId.length >= 1 &&
lastFetchedMessageTime
) {
let firebaseQuery = firestore()
.collection('chats/' + chatId + '/messages')
.orderBy('sentAt', 'desc');
/* if its new chat so it should not apply this filter
becuase there is no data already on firestore
so it will not listen to the changes we are going to make now.
Like if we are going to send a new message */
if (!isNewChat) {
firebaseQuery = firebaseQuery.endAt(lastFetchedMessageTime);
}
firebaseQuery.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
const message = change.doc.data();
message.id = change.doc.id;
// It will not process any change of type 'added'. if the message is sent before the last time of the first batch.
if (
change.type == 'added' &&
message.sentAt.seconds < firstBatchLastMessageTime.current.seconds
) {
return;
}
// Handle the specific change made to the message (e.g., update UI, apply changes, etc.)
switch (change.type) {
case 'added':
// Handle newly added message
handleNewDocumentCallback(message);
break;
case 'modified':
// Handle modified message
handleChangeDocumentCallback(message);
break;
case 'removed':
// Handle removed message
handleDeleteDocumentCallback(message);
break;
}
});
});
}
}, [chatId, isNewChat, lastFetchedMessageTime]);