Building web chat with ReactJS and Firebase

Duy Tran
7 min readNov 23, 2018

--

Previously, I had an article showing how to make a chat app with Flutter at here.

Today, I’ll do the same things, but with ReactJS to build a web chat, then we can send messages between app and web to see interest things.

This web also has the following features:

  • Sign-in by Google account.
  • Chat one to one with other users (send text, image, and sticker).
  • Update profile and avatar.

Video demo

I also deployed this project for testing at here:

flutterchatdemo.firebaseapp.com

Let’s get started

1. Creating the project on Firebase

After creating the project, at Project Overview, click Add app and select web platform.

Save this config and we’ll use it later

Start the Cloud Firestore and Storage

Then enable sign-in method with Google

Now you’re done this step, don’t need to add or init any data manually at Firebase.

Rules at Storage:

Use this mode for storage to accept only image file type

Rules at Database:

And this mode for database

2. Installing plugins

Runnpm install --save firebase to install firebase package, it helps us use login, database, and storage service.

Also install moment, react-loading, react-toastify and react-router-dom to handle date time, show loading, toast and navigate (check out my package.json for more).

3. Configuring firebase and define web app flow

Create a file MyFirebase.js to config and use some firebase services

The web app will have 3 screens, all of them are inside root.js then we can use react-router-dom to navigate between these screens.

  • Login screen (auth by google).
  • Main screen (includes 3 components, list user, welcome board and chat board).
  • Profile screen.
The flow for this web app

4. Rendering root screen with navigation

We’ll use import { BrowserRouter, Switch, Route } from 'react-router-dom' to navigate between screens.

Not like React Native (just call show() is enough), toast at ReactJS need a component (ToastContainer) inside render function, so we should render it at root, then all child screens can use it and catch the case show toast at this screen, but immediately navigate to next screen (eg: toast login success).

Root screen

5. Rendering login screen

Simple login screen with google account
Require sign in with popup

If authenticate success, the variable result giving some info like displayName, email, photoURL,… and after that, we should check if this user is new? if true, we write to the database. The last thing to do is save to localStorage (no matter new or not) for easier to use later.

Notice that we should always check login at this screen since the user re-access this page, auto navigate them to the main screen instead require login.

6. Rendering main screen

Notice in this screen, we have 3 components, list user at the left, and welcome board/chat board at the right. When a user navigates to this screen at first, since they’re not chatting with any user, we should show welcome board, and when they choose another user to start the conversation, we should render chat board with loading that conversation’s history.

UI main screen

This screen also needs to check login to prevent invalid navigate since the user can type exact path URL to browser’s address bar.

Check login and get list user

Rendering list user

List user

If logged in, we load list user and render them. A thing interesting at here is ReactJS doesn’t have listview built-in or something similar (I worked with RN before and quite surprise with it). So we need to loop an array (list users) and render each item by ourselves, then add all item views to an array view (viewListUser) and return it, so we just call this function at render is fine.

Rendering list user

Rendering history messages

The welcome board is just simple UI, so I’ll ignore it to keep a short article, check out my source code at WelcomeBoard.js.

Chat board

When the user chooses other people, let’s load the history and render them at initial. Notice that onSnapshot at here will load all child node first like the docs said, so we don’t need to separate load history and listener into 2 parts.

Notice that when user A (current user) chat with user B (peer user), the problem is which groupChatId we’ll use to read and write data chat to the server since conversation of AB need save at the same node? So my idea is hashing the A’ uid and B’ uid, then creates a string like this.

Loading history and listening new data added

So at user A or B, they both read and write data at the same Firebase node.

The variable this.removeListener be called to remove listening new data added at the current node when the current user leave this screen or switching to chat with other users.

About rendering list messages, it’s just some check left and right message with CSS style, please refer Chatboard.js for more details.

Handling input form

Input form

The object we can send is image, sticker, and text. Notice that we need a field like a type to detect it is the normal message, image or sticker to render appropriately.

With image, first we upload it (it has a callback with return URL after upload success), so the message content should be the URL. Then just using <img/> to render it.

Choose image from window and upload it

Filter file type to only accepting image, but we need to check the prefix file type too since accept="image/*" only provides a hint to the browser as to what file-types to display to the user, but this can be easily circumvented.

With sticker, the message content should be the file name (we stored local these stickers) and when render it, check if file name match? If true, use <img/> to show this sticker.

Open list sticker

Sending messages

When the user clicks an icon or hit enter to send a message, image or sticker, we need to check if the content is not empty, if true, write new data to the node appropriately. When add data success, onSnapshot will catch the event and render to the list.

Write new data to server

Handling logout

We should show the confirmation at first
Performing log out action

Nothing special here except remember clear the localStorage to prevent user use exactly path URL to go main screen without logged in (I use local data to check user logged in or not).

7. Rendering profile screen

Profile screen

You can add more field if you would like, but in this demo scope, I just use name, description, and avatar for quickly.

First, we need to check login like the main screen to prevent the user goes to this screen directly by path without login.

When the user chooses a file from their computer, we assign this file to a variable name this.newAvatar since we don’t know user update their info with or without a new avatar, if they update avatar too, first we need upload this file, then get URL and add to user node this info.

Upload avatar

After updating info success, we should save it to local for easier to use later.

Update info on both server and local

Cloud Firestore structure

I can’t public my firebase server, so just giving a little information to help you imagine the structure.

Node users
Node messages

--

--