How to Handle Real-Time Post Notifications in Your Applications
Recently, I was working on an application. And that application was a Thread Clone, the newly launched social media platform.
The tech stack I was using was React for the front end and Firebase for authentication, real-time database, and for file uploads.
As I was building the app, I thought wouldn’t it be cool if I add a Real-time notifications feature in the app, that updates the user when someone likes or comments on your thread?
And so I started writing the code.
Prerequisites
You must know React and Firebase in order to proceed with the things explained below. However, this can also be implemented in different databases like SQL or NoSQL.
Before implementing this notification feature, we need a few things ready. Since the context of this feature is from the point of social media applications like Facebook, Threads, Twitter, Linkedin, and more, we need a few parameters ready.
If you want to learn how to build a Thread clone or a Linkedin Clone, here are the links to those videos:
Code a Threads Clone using React and Firebase: https://youtu.be/_itNFs2cUnY
Build a Linkedin Clone using React and Firebase: https://youtube.com/playlist?list=PLWgH1O_994O-vRmOAKtq8VIM6XIC6xwkb
Let’s talk about those parameters now.
const notificationData = {
userName: auth.currentUser.displayName,
recipientUserId: recipientUserId,
senderUserEmail: auth.currentUser.email,
senderUserId: auth.currentUser.uid,
type: "like",
threadID: threadID,
threadData: threadData,
timestamp: moment().format(),
isRead: false,
};Take this payload as an example. We have an object called notificationData. In that object, we have parameters like username, recipientUserId, senderUserEmail, senderUserId, and more. Let me explain these.
userName: The Current Username of the person who has logged in.
recipientUserId: The ID of the person who will recieve notifications.
senderUserEmail: The Current Username of the person who has logged in.
senderUserId: The Current UserID of the person who has logged in.
type: "like" : The type of Notification. It can be a like or a comment.
threadID: The ID of the Thread, or a Post.
threadData: The contents of the Thread, or a Post
timestamp: The current timestamp.
isRead: false: The status of the notification, if it has been read or not.For the current userName, senderUserEmail, and senderUserId, it belongs to the current user who has logged in. We need these inputs in order the show who interacted with your thread. If I have been logged in, these data should be mine. We are getting these params from the auth from Firebase Auth.
For the recipientUserId, we need this input to notify the user that someone has liked or commented on your post. If I am liking a thread or adding a comment on one, we need the ID of the user who made the thread so that we can filter through the notifications.
We also have isRead, which is a boolean value to check if our notification has been read or not. If we click a notification, we can mark it as read, just by changing the isRead to true.
The rest of the parameters are threadID, which is the ID of the thread and threadData is the contents of the thread.
Now, how to get these inputs is up to you. If you want to build a social media application, simply refer to the videos above.
Adding a Notification Collection in a Firebase
First of all, we need to create a reference to Firebase. Let’s create it.
let notificationCollection = collection(database, "notification");Now, we must have a function for handling likes. If that function runs, we send a like to a respective thread. Take the below function as an example.
export const likeThread = (
userId,
recipientUserId,
threadData,
threadID,
liked
) => {
try {
let docToLike = doc(likeRef, `${userId}_${threadID}`);
let docToNotify = doc(
notificationCollection,
`${recipientUserId}_${threadID}`
);
if (liked) {
deleteDoc(docToLike);
deleteDoc(docToNotify);
} else {
setDoc(docToLike, { userId, threadID });
if (userId !== recipientUserId) {
const notificationData = {
userName: auth.currentUser.displayName,
recipientUserId: recipientUserId,
senderUserEmail: auth.currentUser.email,
senderUserId: auth.currentUser.uid,
type: "like",
threadID: threadID,
threadData: threadData,
timestamp: moment().format(),
isRead: false,
};
setDoc(docToNotify, notificationData);
}
}
} catch (err) {
console.log(err, "error");
}
};We are having a likeThread function that is taking some parameters that I previously discussed. We have the userId here as well, which denotes the ID of the current user.
We also have a liked property, which is a way to check for likes on a thread. When we like it, it will become true, else it will be false.
let docToNotify = doc(
notificationCollection,
`${recipientUserId}_${threadID}`
);We have this doc function from Firebase Firestore, that is combining the recipientUserId with the threadID as a unique string and that will be the ID of the notification for a Thread in the Firestore Database.
if (liked) {
deleteDoc(docToLike);
deleteDoc(docToNotify);
} else {
setDoc(docToLike, { userId, threadID });
if (userId !== recipientUserId) {
const notificationData = {
userName: auth.currentUser.displayName,
recipientUserId: recipientUserId,
senderUserEmail: auth.currentUser.email,
senderUserId: auth.currentUser.uid,
type: "like",
threadID: threadID,
threadData: threadData,
timestamp: moment().format(),
isRead: false,
};
setDoc(docToNotify, notificationData);
}
}We have two if statements in the code block. The first one is that if the thread is already liked and we unlike it, we will simply delete the notification document for that thread from the database using deleteDoc while passing the collection reference, which is docToNotify.
The second one is just to check if the userId which is our own ID is not equal to the recipientUserId from the thread. It simply checks for a case where we like or add a comment on our own post. In this scenario, we cannot send a notification to ourselves. That is not a proper case. But keep in mind that the addDoc function to send likes will be outside the second if statement. This is because we can like our own threads, but not cannot get notifications for us.
setDoc(docToNotify, notificationData);Then we simply add these data to Firebase Firestore using the setDoc function with the parameters docToNotify, which is which user to notify, and the payload which is notificationData.
As for the comments, we can do the same thing we did for likes. The only difference will be the type key that is a comment if we are sending a notification for comments.
export const postReplies = async (
recipientUserId,
threadData,
userId,
threadID,
reply,
timeStamp,
currentUserName
) => {
try {
addDoc(repliesRef, {
threadID,
reply,
timeStamp,
name: currentUserName,
});
if (userId != recipientUserId) {
const notificationData = {
userName: auth.currentUser.displayName,
recipientUserId: recipientUserId,
senderUserEmail: auth.currentUser.email,
senderUserId: auth.currentUser.uid,
type: "comment",
threadID: threadID,
threadData: threadData,
timestamp: moment().format(),
isRead: false,
};
addDoc(notificationCollection, notificationData);
}
} catch (err) {
console.log(err);
}
};Getting Notifications for a particular user
To get notifications for a particular user, we need the userId, which is the current ID of the user who has logged in.
export const getNotifications = async (userId) => {
const getNotifQuery = query(
notificationCollection,
where("recipientUserId", "==", userId),
orderBy("timestamp", "desc")
);
onSnapshot(getNotifQuery, (response) => {
console.log(
response.docs.map((doc) => {
return { ...doc.data(), id: doc.id };
})
);
});
};We need to create a query to check if the recipientUserId is equal to the userId. This means the thread is our own, and we should receive a notification for that thread if anyone interacts with it by likes or comments. We also have orderBy to order the notifications in descending order. This will give us all the notifications for a current user who has logged in.
Displaying Notifications in the UI
Displaying notifications in the interface is pretty simple.
import React from "react";
import useFetchNotifications from "../Hooks/useNotifications";
import { useLocation } from "react-router-dom";
import Notifications from "../Components/Notifications";
export default function NotificationsPage() {
let { notifications } = useFetchNotifications();
return (
<div>
<ul className="notification-ul">
{notifications.map((notification) => (
<div key={notification.id}>
<Notifications notification={notification} />
</div>
))}
</ul>
</div>
);
}We can simply get notification data. Here, I am having a custom react hook called useFetchNotifications(), from which we are destructuring the notifications array.
Then we are simply mapping the notifications using the map function.
Our notification page will be like this. You can design the way you want to and also add user profile images for the user who liked or commented on your thread.
When we click a notification, we have to make it invisible or inactive. It means we have read it.
Let’s have a function for this operation.
export const readNotifications = async (id) => {
let docToUpdate = doc(notificationCollection, id);
updateDoc(docToUpdate, { isRead: true });
};This function will take the id of the notification and update that particular notification’s isRead property to true.
When we click the notification, it will disappear. This is totally up to you what you want to happen in this case.
Showing the count of Notifications
We can also show the count of notifications in the bottom bar.
To do this notification count, we need to filter through the notifications array and find out if the notification isRead property is false. If it is false, which means it has not been read yet. If it has not been read yet, it means we can show it’s count.
let isRead = notifications
.filter((item) => item.isRead === false)
.map((notif) => notif.isRead);Let’s have a count badge adjacent to the notification icon. And here, we should the length of the isRead array to find the count of the total notifications.
<div className="active-notifications">{isRead.length}</div>We will only show the count if the length is more than zero. In that case, we can have a condition that checks for the length of the isRead array length.
{isRead.length ? (
<div className="active-notifications">{isRead.length}</div>
) : (
<></>
)}And folks, this is how we handle a Notification system for a Thread application or any social media application.
If you want a video version, here it is:
How to Implement a Notification Feature in a Web App: https://youtu.be/03OvR8I3EXg
