I’m implementing 1:1 chatting on my Next.js website.
I’m encountering an issue with updating and properly unsubscribing listeners for unread messages count in Firebase Realtime Database. Here’s the scenario:
I have a function getUnreadMessages
that listens to changes in the database to calculate the total unread messages count for a user across multiple chats. The function sets up onValue listeners for each chat’s unread messages count and accumulates the count. However, when I use another function setUnreadMessages
to update the count for a specific chat, the listeners established by onValue do not update accordingly. This leads to outdated counts being returned by getUnreadMessages
.
This is my firebase database structure
{
"chats": {
"-NsiGVfPascYlr1ukPVP": {
"lastMessage": "sdf",
"members": {
"65dd85133a58aa82bc050081": true,
"65e71757ff707f0da0b9e8d8": true
},
"timestamp": 1710242413808,
"unreadMessages": {
"65dd85133a58aa82bc050081": 0,
"65e71757ff707f0da0b9e8d8": 5
}
},
"one": {
"lastMessage": "안녕",
"members": {
"65dd85133a58aa82bc050081": true,
"65df0955c7a84d629f8a54a5": true
},
"timestamp": 1710220731677,
"unreadMessages": {
"65dd85133a58aa82bc050081": 0
}
}
},
"users": {
"65dd85133a58aa82bc050081": {
"chats": {
"-NsiGVfPascYlr1ukPVP": true,
"one": true
}
},
"65df0955c7a84d629f8a54a5": {
"chats": {
"one": true
}
},
"65e71757ff707f0da0b9e8d8": {
"chats": {
"-NsiGVfPascYlr1ukPVP": true
}
}
}
}
export async function getUnreadMessages({
userId,
}: {
userId: string;
}): Promise<number> {
let totalUnreadMessages = 0;
const userChatsRef = ref(realtimeDB, `users/${userId}/chats`);
const snapshot = await get(userChatsRef);
const chatIds = snapshot.val() || {};
const promises: Promise<void>[] = [];
for (const chatId in chatIds) {
const userChatRefRef = ref(
realtimeDB,
`chats/${chatId}/unreadMessages/${userId}`
);
const promise = new Promise<void>((resolve, reject) => {
onValue(
userChatRefRef,
(snapshot: DataSnapshot) => {
const unreadMessagesCount = snapshot.exists() ? snapshot.val() : 0;
totalUnreadMessages += unreadMessagesCount;
resolve();
},
(error) => {
console.error(`Error listening to chat ${chatId}:`, error);
reject(error);
}
);
});
promises.push(promise);
}
await Promise.all(promises);
return totalUnreadMessages;
}
export async function setUnreadMessages({
chatId,
userId,
isReset,
}: {
chatId: string;
userId: string;
isReset?: boolean;
}): Promise<void> {
if (!chatId || !userId) return;
try {
const unreadMessagesRef = ref(
realtimeDB,
`chats/${chatId}/unreadMessages/${userId}`
);
let unreadMessagesCount = 0;
if (!isReset) {
const unreadMessagesSnapshot = await get(unreadMessagesRef);
if (unreadMessagesSnapshot.exists()) {
unreadMessagesCount = unreadMessagesSnapshot.val() || 0;
}
}
await set(
unreadMessagesRef,
isReset ? unreadMessagesCount : ++unreadMessagesCount
);
console.log(
`Unread messages count for user ${userId} in chat ${chatId} reset.`
);
} catch (error) {
console.error("Error resetting user unread messages count:", error);
throw error;
}
}
and I use react-query to fetch the data.
const { data: fetchedUnreadMessagesCount } = useQuery({
queryKey: ["unreadMessagesCount"],
queryFn: () => getUnreadMessages({ userId: session?.user.id }),
enabled: !!session?.user.id,
});
My firebase version is v10.8.1
.
Please tell me where I’m doing wrong? Thank you in advance!
2
Answers
I created a custom hook that listens the
onValue
If you want to listen for realtime updates to a value, you:
return
that value, as areturn
statement can only run once.Promise
can only resolve once.If you want to listen for updates, that can happen multiple times – so you need to use a mechanism that allows that. This is in fact precisely why the
onValue
API takes a callback method, rather than returning a Promise. You’ll need to do something similar.