Building a real time chat application with React, Firebase and Redux Saga

Rithika Chowta
5 min readJun 26, 2019

Hi folks!

Most applications these days, from Uber to Swiggy, involve a chat feature. It may be with another user of your application or a bot. Building this feature can be tricky. We need the changes in data to reflect real time. Web sockets are one solution. The alternative is to leverage the power of Firebase RTDB ( Realtime DataBase). An added benefit is that this solution requires no backend. All of the data is exchanged only between your client application and Firebase.

The Firebase Real-time Database is a cloud-hosted database. Data is stored as JSON and synchronized in real-time to every connected client. This means that you don’t have to actively poll for updates to your data. Any updates are automatically pushed to the client. Connect to the chatroom once and you’re good to go.

Read on to find out how to do this.

1. Setup your Firebase project

This step is pretty straight-forward. Sign in to the Firebase console and create a new project. Install the firebase npm package in your client application and initialize the Firebase SDK.

2. Building the components

This is the fun part. Build components for your chat message bubbles, the input field and a container to render all your messages one by one. Here is a simple implementation. Also, I have used Bootstrap to make styling my components easier.

The changeHandler updates the current text in the input to our parent component’s state. clickHandler triggers the function to write a message to the chatrrom.

ChatMessage is our message bubble component. Each bubble can have 3 parts: a separator to display the date if there are two consecutive messages that were sent on 2 different days, the header to display the image of the other participant in the chatroom and the timestamp and the body which shows the actual message.

MyMessageBody simply shows messages sent by us with a different styling and right aligns the bubble, as commonly seen in chat applications.

Great! We’ve got all our building blocks in place. Now let’s put it all in a container.

<ChatBody/> receives an array of messages as props and renders each one after the other. This component is also where we are including our logic to automatically scroll to the bottom of the chat container whenever the component receives a new message.

3. Setting up Saga functions

fetchChat is triggered when our component is first mounted. It connects to the firebase chatroom and fetches all the data present in the chatroom. Once a snapshot of the data is fetched, a success action is dispatched to trigger the postprocessing of the data.

updateChatState is triggered only when there is an update to the data in the chatroom. It receives the latest update as parameter and dispatches the success action.

All of the actions have been defined in a separate file as constants.

4. Setting up reducers

The reducer is where we package the data we got from Firebase into a form that can be rendered by our UI components. The messages are received as an array of objects, where each object is a message.

After the processor functions are called, the transformed messages array is updated to the Redux state. Our Chat component receives the new state and updates any changes to the UI. The next section describes each processor function.

We are also setting a flag isFetching which can be used to render a loader while the messages are loading. In case, there is an error connecting to Firebase, that is also updated to the state so that a suitable error message can be shown.

5. Setting up utility functions

This is where all the heavy lifting happens.

prepareMessages calls setMessageProps for every message in the message array. It also calls a function which sets the visibility of the message header.

setMessageProps sets certain properties for each message object, required to decide how to render the message. If fromSelf is true, MyMessageBody is used to render the body of the message. The avatar to be displayed in the message header is set and the Epoch timestamp is converted to a human readable format.

setHeaderDisplay is used to decide whether to display of the header of a message or not. If a message has the same header as the previous one, they are simply stacked together under a single header. This function also handles the logic to decide whether to display a separator between two messages or not.

6. Putting it all together

Now we are finally ready to build our main component <Chat/>. We will also be setting up the store, initializing Firebase SDK and writing messages to the chatroom from this component

When this component is mounted, the Firebase app is initialized. One tricky problem was to figure out how to know what chatroom to connect to without getting the data from our application’s server. Thankfully, there is a work around. Every chatroom is always named by sender and receiver IDs arranged lexicographically. So as long has we have the IDs of the sender and receiver, we know what chatroom to connect to.

If there is some data present at the chatroom, it is fetched and the FETCH_CHAT action is dispatched. The saga kicks in at this point and takes over the rest of the work.

We also set up a listener to listen to updates to the chatroom. When there is an update, UPDATE_CHAT is dispatched along with the new message object.

sendMessage is triggered whenever we want to write a message to chatroom. This pushes an object to the database containing the sender ID, receiver ID, the message text and the current Epoch timestamp.

updateStateOnChange is used to maintain the current value of the input field.

The entire component is wrapped in <Provider> so that it receives the Redux State.

Whew! If only there was a library to do all this work for us!

Well actually there is! This entire feature can be integrated into your app using a single React component. All you have to do is render the component and pass the required props.

Do try it out and review!

Code is up on my Github. Also, check out the demo here.

Thanks for reading!

--

--