King Somto
5 Nov 2021
•
5 min read
Chat applications are fun to use they enable us to send messages to other users old and new alike,
Our series started at us just building simple react components then progressed to us being able to implement firebase-cloud functions
in our application , using firebase-cloud functions
we realized that we are able to offload some firebase operations we might have done on the frontend to the backend, this enables to perform some powerful and sensitive operations on the backend examples would be
Validate login
Validate Signup form
Filter friends
Perform operations when saving messages
Load ChatId
Handle payments (if needed)
Many more operations
We set out to build a messaging app using only firebase (as a database) and react, so far we have been able to go from building an app that was similar to a forum app to adding personalized chat messages between individual users to our application, now we would like to add more features to our application to make the application more like WhatsApp, and we are going to start with adding Read
receipts just like apps like WhatsApp and Facebook messenger.
To build this we have to be able to have a flag in every message telling us if the message has been read or not so we can render a tic on the messages sent after they are viewed. Our database would resemble the below image.
We have to set the read
flag to true whenever a message component has been rendered, ideally, this is meant to be done on the backend, but I think this method is the cleanest considering our current previous implementations also firebase doesn't offer a `onread hook for objects, (so if you're a firebase engineer reading this help a bro out and add that, thanks).
Our approach would be to look create a function that is able to find our message object in the chat
collection and append a read
flag as `true, this can only be done by the user receiving the message, once done it would be rendered by our application, thanks to the way firebase libraries handles database updates
Add the Tick
component to our project folder
To do that download any Tick
SVG of your choice and store it in the assets
folder, then create an index.js
file in that folder to help export the SVG element as a React
component.
Paste​​
import {ReactComponent as Tic} from './tic.svg';
export {Tic}
Edit our messagesBox.jsx
component to render the Tick
component.
Let's start by adding some styling and adding the Tick
Component
import React from 'react';
import style from 'styled-components';
import { Tic } from '../assets';
import { db } from '../config/firebase';
export default function Message({ data, name, chatId }) {
const { message, id, sender, ...rest } = data;
const Message = style.div`
min-height: 21px;
max-width: 90%;
min-width: 50%;
position: relative;
width: max-content;
background: ${sender !== name ? 'pink' : 'blue'};;
text-align: left;
padding: 10px;
border-radius: 10px;
color: white;
margin: 15px 0;
float: ${sender !== name ? 'left' : 'right'};
div {
}
svg{
height: 15px;
width: 15px;
position: absolute;
color: pink;
bottom: -7px;
right: 10px;
background: white;
border-radius: 50%;
padding: 3px;
}
`;
return <Message><div>
<p>
{message}
</p>
{ seen && sender === name && <Tic/>}
</div></Message>;
}
{ seen && sender === name && <Tic/>}
Here the Tick
SVG does not render except if the message has been marked as seen.
We pass more logic data into the component so it can make a database call to mark a message as seen. We need to edit the messages.jsx
to pass in the chatId
into the messageBox
component.
export default function Messages({ name }) {
const Messages = style.div`
padding: 20px;
height: calc(100% - 64px);
overflow-y: scroll;
`;
const [messages, setMessages] = React.useState([]);
const [chatId, setchatId] = React.useState(false);
React.useEffect(() => {
const receiver = window.location.href.split('/')[4]
try {
allFunctions.getChatID({
userName: receiver
}).then(({ data }) => {
const { chatId = '' } = data;
setchatId(chatId)
db.ref('chats').child(chatId).on('value', (snapShot) => {
let chats = [];
snapShot.forEach((snap) => {
const dataToShoww = {
...snap.val(),
id:snap.key
}
chats.push(dataToShoww);
});
setMessages(chats);
var element = document.getElementById('messages');
element.scrollTop = element.scrollHeight - element.clientHeight;
});
}).catch((error) => {
console.log(error)
})
} catch (error) {
console.log(error)
}
}, []);
return (
<Messages id='messages' >
{messages.map((data) => {
return <Message name={name} data={data} chatId={chatId} />;
})}
</Messages>
);
}
Breaking it down
const { chatId = '' } = data;
setchatId(chatId)
Here we set the chatId variable to be passed on to the message components after we run the getChatID
function.
Now we are able to pass all the data to the messageBox.jsx
component and perform a function call to mark the message as read
.
Our approach for this implementation is to mark a message object as seen when we render it on the app, we have only 2 conditions that perform an indication that a message should be marked as seen.
const [seen, setSeen] = React.useState(rest.seen)
React.useEffect(() => {
console.log({data,sender,name})
if (seen || sender === name) {///sen this guys or am the sender meaning i need a read receipt
return
}
markAsSeen(id, chatId)
///check if seen?
}, [])
const markAsSeen = (messageId, chatId) => {
try {
console.log({
messageId,chatId
})
//mark the message as read
const messageeRef = db
.ref('chats').child(chatId).child(messageId)
console.log({
messageeRef
})
messageeRef.update({///update the message using the referece
seen: true///updates the
})
} catch (error) {
console.log(error)
console.log('Can not update the state of the object');
}
}
Let's break it down
React.useEffect(() => {
console.log({data,sender,name})
if (seen || sender === name) {///sen this guys or am the sender meaning i need a read receipt
return
}
markAsSeen(id, chatId)
///check if seen?
}, [])
The markAsSeen
function is run in the useEffect
function, it checks if the message is marked as seen
or if am the sender
if any of the conditions are met it calls the return function.
//mark the message as read
const messageeRef = db
.ref('chats').child(chatId).child(messageId)
console.log({
messageeRef
})
messageeRef.update({///update the message using the referece
seen: true///updates the
})
The code above uses the information passed in the component to create a message ref messageeRef
using this reference we are able to update our database by adding a read flag and setting the value of the object to true.
Now we can run our codebase for our frontend and backend if you are not able to remember the commands for the application(frontend or backend) I suggest you check out my previous articles on firebase functions and just set up the frontend app
Now let's look at our output to see what we have.
We do this by updating the message object whenever a message has been rendered on a user's device by passing the chatID
and the messageId
in our component and making it call firebase to update the chat object.
Moving further, this series has been a steady progression of implementing simple firebase features to be able to achieve or simulate activities we can find on a chat application, the next tutorial would focus on sending other users images and 30-sec videos, and also possibly audio notes. Stay tuned
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!