This tutorial is outdated. We're currently working on an updated version. In the meantime, for instructions on building a chat app using CometChat, please visit our documentation.
App and web development have come a long way over the last few years. We use a lot of chat apps every day. One of the most widely used features is live chat including text chat and voice chat such as Facebook, Discord, WhatsApp, and so on. Using the React CometChat UI Kit, Firebase backend services, you will learn how to build the Discord clone.
Follow along the steps to build a Discord clone that will allow users:
1. Users
A way for end-users to signup and sign out (email & password is sufficient)
Users to send, receive and accept/reject friend requests.
Users can invite other users to join the channels
2. Channels
Text Channels
- List of all text chat channels in sidebar
- Users can create channels.
- Users can send and receive messages in channels
- Please include basic chat features like emojis, GIFs, media sharing, mentions, threads
Voice Channel
Ability to create voice channels
Chat over voice and video
Screen Sharing
3. The ability for users to chat with friends directly via DM.
4. Login the logged-in user to CometChat.
5. Create a user-friendly and attractive UI like the Discord app.
Prerequisites
To follow this tutorial, you must have a degree of understanding of the general use of React.js. This will help you to improve your understanding of this tutorial.
Installing the App Dependencies
Step 1: You need to have Node.js installed on your machine.
Step 2: Create a new project with the name discord-clone by running the following statement.
Step 3: You need to install some dependencies such as CometChat, Firebase, Validator, Uuid.
*Note: At this time of writing this tutorial, we are using the Firebase SDK with the version @8.9.1
Configuring CometChat SDK
Head to CometChat Dashboard and create an account.
Register a new CometChat account if you do not have one.
Add real-time chat with minimal effort using CometChat
2. After registering a new account, you need to the log in to the CometChat dashboard.
Log in to the CometChat Dashboard with your created account
3. From the dashboard, add a new app called "discord-clone".
Create a new CometChat app - Step 1
Create a new CometChat app - Step 2
4. Select this newly added app from the list.
Select your created app
5. From the Quick Start copy the APP_ID, REGION, and AUTH_KEY, which will be used later.
Copy the the APP_ID, REGION, and AUTH_KEY
6. Navigate to the Users tab, and delete all the default users (very important).
Navigate to Users tab and delete all the default users
7. Navigate to the Groups tab and delete all the default groups (very important).
Navigate to Group tab and delete all the default groups
8. Create a file called **.env** in the root folder of your project.
9. Import and inject your secret keys in the **.env** file containing your CometChat and Firebase in this manner.
10. Make sure to include **.env** in your gitIgnore file from being exposed online.
Setting Up Firebase Project
According to the requirements of the Discord clone, you need to let users create a new account and login to the application, Firebase will be used to achieve that. Head to Firebase to create a new project and activate the email and password authentication service. This is how you do it:
To begin using Firebase, you’ll need a Gmail account. Head over to Firebase and create a new project.
Firebase
Firebase provides support for authentication using different providers. For example, Social Auth, phone numbers, as well as the standard email and password method. Since you will be using the email and password authentication method in this tutorial, you need to enable this method for the project you created in Firebase, as it is by default disabled.
Under the authentication tab for your project, click the sign-in method and you should see a list of providers currently supported by Firebase.
Firebase Authentication
Next, click the edit icon on the email/password provider and enable it.
Enable Firebase Authentication with Email and Password
Now, you need to go enable Firebase Realtime Database. We will Firebase Realtime Database to store the information of the users in the application. Please refer to the following part for more information.
Choose “Realtime Database” option
Click on “Create Database"
Select location where you realtime database will be stored
Select “Start in test mode” for the learning purpose
Please follow the guidance from Firebase. After following all steps, you will see the database URL. If you just need to update the “FIREBASE_REALTIME_DATABASE_URL” variable in your “Constants.java” file with that value.
Database URL
On the other hand, your Firebase real-time database will be expired in the future. To update the rules you just need to select the “Rules” tab and update the date/time in milliseconds as you can see in the image below.
Update Firebase Realtime Database Rules
The below images demonstrate the data structure of the application. A user should have an avatar, an email, full name, id, and list of friends. On the other hand, if the end-user has sent a friend request to another user, that request is stored in the waiting field. Aside from that, if someone also sent a friend request to that user, that request is stored in the pending field.
Data Structure - User
According to the requirements, the end-users can create the text/voice channels. The channel’s information should be stored in the Firebase Realtime Database. You can refer to the below images to understand the data structure of the text channels and the voice channels.
Data Structure - Text Channel
Data Structure - Voice Channel
Configuring Styling for the Application
Inside your project structure, open the index.css files and paste the codes here. index.css file will contain all CSS of the application.
Configuring Images for the Application
In this project, we need to create a folder called “images” inside the root folder. The “images” folder is used to store all of the images that will be used to build the application. To get the images, you can refer to this link.
Creating React Context
In this application, we need to pass some data between the components. We can have several options to achieve that such as using state management libraries, RxJS, and so on. However, we can get in-built support from React by using the React Context API. the React Context API helps us pass data through the component tree without passing down at every level. Please create the “context.js” file inside the “src” folder. The full source code of the “context.js” file can be found here.
Initializing CometChat for the Application
The below codes initialize CometChat in your app before it spins up. The App.js file uses your CometChat API Credentials. We will get CometChat API Credentials from the .env file. Please do not share your secret keys on GitHub.
Actually, App.js does not contain only the above code. It also contains other business logic of the application. The full source code of App.js file can be found here.
Configuring the Firebase File
You need to create a “firebase.js” file inside the “src” folder and you need to enable Firebase realtime database. This file is responsible for interfacing with Firebase authentication and database services. Also, it makes ready our google authentication service provider enabling us to sign in with google. Secret keys will be stored in the .env file. As mentioned above, please do not share your secret keys on GitHub.
Project Structure
The image below reveals the project structure. Make sure you see the folder arrangement before proceeding.
Project Structure
Now, let's make the rest of the project components as seen in the image above.
The App.js File
The App.js file is responsible for rendering different components by the given routes. For example, it will render the login page if the user has not logged in, yet or it renders the home page if the user has signed in to the system. On other hand, it will be used to initialize CometChat. The full source code of the App.js file can be found here.
The Loading Component
The Loading Component
The loading component will be shown when the system performs some side effects such as interacting with Firebase or calling CometChat APIs, and so on. This component will be used to increase user experience. If we do not have this component, the end-users cannot know when the data is loaded successfully. Please create the “components” folder inside the “src” folder, and create the “common” folder inside it. The “common” folder is used to store the common components that will be used in different places in your application. After that, you need to create the “Loading.js” file inside the “common” folder. The full source code of the loading component can be found here.
The Login Component
The Login Component
This component is responsible for authenticating our users using the Firebase authentication service. It accepts the user credentials and either signs him up or in, depending on if he is new to our application. See the code below and observe how our app interacts with Firebase and the CometChat SDK. Please create the “login” folder inside the “components” folder, and create the “Login.js” file inside the “login” folder. The full source code can be found here.
We store the authenticated Information to the local storage for further usage such as getting that information in different places in the application, prevent the end-users come back to the login page after signing in to the application.
To login to the CometChat, we call the cometChat.login function, you can refer to the below code snippet for more information.
The above code indicates that we are using withModal as a higher-order component. This higher-order component will be used to reuse the code of showing and hiding the custom modal. In this case, we want to show the sign-up modal to let end-users register new accounts. We will discuss the sign-up component in the following section.
The withModal - Higher-Order Component
As mentioned above, we would like to show the SignUp component as a modal. Actually, we have multiple modals in the application, and we should avoid duplicating common functionalities such as showing/hiding the modals, and so on. To reuse the common logic, we can have several options. In this project, we will create a higher-order component called “withModal”. That higher-oder component helps us to avoid duplicating code and we can customize the UI for the modals. Please follow the below code snippet for more information. Please create the “Modal.js” file inside the common folder. The full source code can be found here.
The Sign Up Component
The SignUp Component
The sign-up component will help end-users to register new accounts. This component will do two things. The first thing is to register new accounts on Firebase by using the Firebase authentication service. Aside from that, it also registers new accounts on CometChat by using the CometChat SDK. Please create the “register” folder inside the “components” folder, and create the “SignUp.js” file inside it. The full source code can be found here.
To create a new CometChat account, we call cometChat.createUser function. You can refer to the below code snippet below for more information.
Add the CometChat UI to Our Application
Before we can use the CometChat Pro React UI kit, we need to add it in our project so that we can reference it. In this case, we are using React UI Kit v3.0. To do that, follow the next steps:
Step 1: Clone the CometChat React UI Kit Repository like so:
Step 2: Copy the folder of the CometChat Pro React UI Kit you just cloned into the src folder of your project:
Copy the cloned folder in to the src folder
React UI Kit Dependencies
Step 3: Copy all the dependencies from the package.json file of the CometChat Pro React UI Kit folder and paste them in the dependencies section of the package.json file of your project.
Step 4: Save the file and install the dependencies like so: npm install
As soon as the installation is completed, you now have access to all the React UI Components. The React UI kit contains different chat UI components for different purposes as you can see in the documentation here. It includes:
1. CometChatUI
2. CometChatUserListWithMessages
3. CometChatGroupListWithMessages
4. CometChatConversationListWithMessages
5. CometChatMessages
6. CometChatUserList
7. CometChatGroupList
8. CometChatConversationList
The Chat Component
CometChatMessages Component
CometChatMessages Component - Voice and Video Calling
The Left Menu Component
The Left Menu Component
In this section, we will create the left menu component as you can see in the above image. To do that, we need to create the “Menus.js” file inside the “common” folder, we create that file inside the “common” folder because it is a common component, it will be reused in different places in the application.
The full source code of the “Menus.js” file can be found here.
The Menu Item Component
In this tutorial, the list of menu items will not be changed. For this reason, we create a static list to store the information of each menu item, each item contains the id and icon. Each item is demonstrated by the Menu component. Please create the“Menu.js” file inside the “common” folder. The full source code of the Menu component can be found here. The Menu component accepts three props
isActive: indicates which menu has been selected, that one should be highlighted.
onItemSelected: attach the event for each menu item.
item: the item information that will be rendered on the UI.
You can refer to the below code snippet for more information.
The Friend Component
The Friend Component
According to the requirement, the end-users can do the following actions:
Users to send, receive and accept/reject friend requests.
The ability for users to chat with friends directly via DM
To achieve the above points, we need to build the Friend component. First of all, we create the “friend” folder inside the “components” folder. The “friend” folder is used to contain all components that help us build the “friend” features. The “friend” folder stores the below components:
<Add />
<Friend />
<Header />
<Main />
<NotFound />
<Pending />
<Pendings />
<RightSidebar />
<Search />
<Sidebar />
<Sidebars />
<SubHeader />
<User />
<Users />
All of the above components will be discussed in this section.
The <Friend /> Component
The Friend Component
The Friend component acts as the wrapper component, it contains other components inside it. You can imagine we are playing Lego and we are trying to connect different pieces to make the final result. Please create the “Friend.js” file inside the “friend” folder. The full source code of the Friend component can be found here.
As you can see from the above code snippet, the Friend component includes two components - Sidebars, and Main. We will create them in the following sections.
The <Sidebars /> Component
The Sidebars Component
The Sidebars component is used to render the list of friends. To build the Sidebar component, please create the “Sidebars.js” file inside the “friend” folder. The full source code of the Sidebars component can be found here.
After the friend request was accepted. The application sends a custom message to tell the sender about that. So the list of friends will be reloaded automatically on the sender side. To listen to the custom messages in real-time, we use the CometChat services. The best practice is to remove the listener if the component has been unmounted cometChat.removeMessageListener(user.id);. You can refer to the below code snippet for more information.
To increase the UX of the application, we will show/update the online/offline status of each friend in real-time. To achieve that, we need to use the CometChat service. You can refer to the below code snippet. Please do not forget to call cometChat.removeUserListener(userPresenseListenerId); to remove the listener after the component has been unmounted.
Each item on the Sidebars component will be demonstrated by the “Sidebar” component. We will create the Sidebar component in the following section.
The <Sidebar /> Component
The Sidebar Component
Please create the “Sidebar.js” file inside the “friend” folder. The Sidebar component accepts two props
Friend: indicates the information about each friend of the authenticated user.
onItemClicked: after the end-user has selected a friend, we need to show the chatbox. Therefore, we need to add the event handler for each item in the sidebar.
The full source code of the Sidebar component can be found here.
The <Main /> Component
The Main Component
The main component will act as the wrapper component. It contains the <Header />, <CometChatMessage />, <Add />, <Pendings />, <RightSidebar /> components. Please do not worry, we will discuss those components in the below sections. The full source code of the main component can be found here.
The <Header /> Component
The Header Component
As you can see in the above code snippet, the Main component contains the Header component. The Header component accepts two props:
selectedOption: the header item which was selected by the end-user.
onItemClicked: we attach the event handler for each item in the Header component so we can know which item would be selected by the end-user, and the application can perform the corresponding actions.
Please create the “Header.js” file inside the “friend” folder. The full source code of the Header component can be found here.
The <RightSidebar /> Component
The RightSidebar Component
The RightSidebar component takes the responsibility for rendering static information. Please create the “RightSidebar.js” file inside the “friend” folder. The full source code of the “RightSidebar” component can be found here.
The <NotFound /> Component
The NotFound Component
The NotFound component is used to render a funny image and increase the UX when there is no data to display. For example, if you do not have any pending requests so the NotFound component will be displayed instead of rendering a blank page. Please create the “NotFound.js” file inside the “friend” folder. The full source code of the NotFound component can be found here.
The <Add /> Component
The Add Component
As mentioned above, the end-users can send friend requests to other users in the Discord clone. Therefore, we have to provide UI to help the end-users can do that. Please create the “Add.js” file inside the “friend” folder. The full source code of the Add component can be found here. The Add component contains the <Search/>, <Users/>, <User/> components. On the other hand, it acts as the container component. It means that it will interact with the APIs, get the response data, and pass that data to the presentational components. Separating the business logic and the UI makes the code be easy to maintain, fix issues, and so on.
The <Search /> Component
The Search Component
The Search component accepts one prop - onItemClicked. It means that whenever the end-users are typing something on the search box, we will send the input keywords to the Add component, the Add component takes those keywords, and then call the Firebase Realtime Database service for searching the corresponding users. Please create the “Search.js” file inside the “friend” folder. The full source code can be found here.
The <Users /> Component
After typing some keywords on the search box, we need to call the Firebase Realtime Database service to get the corresponding users and then render those users on the UI. The Users component will take responsibility for doing that. Please create the “Users.js” file inside the “friend” folder. The Users component accepts two props:
users: the list of users that will be rendered on the UI.
onConfirmShown: this is the callback function and it is triggered after the end-user has selected an item in the user’s list. The purpose of this function is to show the confirmation dialog to ensure that the end-users would like to send a friend request to the selected user.
The <User /> Component
The User Component
In the previous section, we have created the Users component. To render each user on the list, we use the User component, please create the “User.js” file inside the “friend” folder. The User component accepts two props:
user: the user that will be rendered on the UI.
onItemClicked: the callback function that will be triggered after the end-user has selected a user in the list.
So we can see here, the Users component takes the callback function and pass it further down to the User component. The full source code of the User component can be found here.
The <Pendings /> Component
The Pendings Component
After we have received some add friend requests from other users, those requests will be shown on the UI by using the Pendings component. Please create the “Pendings.js” file inside the “friend” folder. The full source code of the Pendings component can be found here. As you can see in the above image, the Pendings component contains the SubHeader component and the Pending component. Aside from. that, the Pendings component performs as the container component, it will interact with the back-end services such as the Firebase Realtime Database service or the CometChat services, and so on. After getting the data from the back-end services, it will pass the data further down to its child. In this case, they are the SubHeader component and the Pending component.
After the pending request has been accepted, we need to call the CometChat API to make two users be friends. After that, as mentioned above, the application sends the custom message so the list of friends will be reloaded automatically on the sender side. You can refer to the below code snippet for more information. For more information about the CometChat API, you can refer to this link.
The <SubHeader /> Component
The SubHeader Component
The SubHeader component accepts only one prop - the title. The purpose of the SubHeader component is to show the number of pending requests for the authenticated user. It would help us increase the UX for the application. Please create the “SubHeader.js” file inside the “friend” folder. The full source code can be found here.
The <Pending /> Component
The Pending Component
As mentioned above, the Pending component is used to render each pending request in the list. Please create the “Pending.js” file inside the “friend” folder. The Pending component accepts three props:
user: the user information which will be rendered in the list.
onAccepted: the callback function after the end-user has selected the “accept” button.
onRejected: the callback function after the end-user has selected the “reject” button.
After the end-user has selected the “accept”/”reject” button, we will update the data in the Firebase Realtime Database. The full source code can be found here.
The Server & Channel Component
According to the requirements, the end-users can do the following actions:
Users can invite other users and channels
List of all text chat channels in sidebar
Users can create channels.
Users can send and receive messages in channels
Include basic chat features like emojis, GIFs, media sharing, mentions, threads
Ability to create voice channels
Chat over voice and video
Screen Sharing
To do the above features, first of all, we need to create two folders inside the “components” folder - “channel” and “server”. The “channel” folder contains the components related to the channels, and the “server” folder contains the components related to the servers.
The Server Component
The <Server /> Component
The server component acts as the wrapper component, it contains other components inside it. Please create the “Server.js” file inside the “server” component. The full source code of the Server component can be found here.
The <Sidebars /> Component
We create the “Sidebars.js” file inside the “server” folder to create the Sidebars component. The Sidebars component shows the list of text/voice channels. Following that, it also contains the option that allows the end-users to create new channels. The full source code of the Sidebars component can be found here.
After someone has created a new channel, other users should know about that in real-time. To achieve that, the person, who created the channel, will send a custom message, other users listen to the custom messages in real-time, and reload the list of channels automatically. You can refer to the below code snippet for more information.
The <Channels /> Component
The Channels Component
As you can see from the above code snippet, the Sidebars component contains the Channels component to render the text channels and voice channels. Please create the “Channels.js” file inside the “channel” folder. The Channels component accepts three props:
title: The title of the channel.
channels: The list of channels.
channelType: The type of the channel - text channel or voice channel - 1 is text channel and 2 is voice channel.
onItemClicked: The callback function which would be attached to each channel, because after the end-user has selected a channel, we need to open the chatbox or start the direct call.
The full source code of the Channels component can be found here.
The <Channel /> Component
The Channel Component
Each channel will be shown via the Channel component. The Channel component accepts three props:
channelType: the type of the channel.
channel: the information of the channel that will be rendered on the UI.
onItemClicked: the callback function that was attached to each channel. After the end-user has selected a channel, that callback function will be triggered.
Please create the “Channel.js” file inside the “channel” folder. The full source code of the Channel component can be found here.
The <Create /> Component
The Create Component
After the end-user has selected the plus icon on the left sidebar. The Create component will be displayed as a modal. Please create the “Create.js” file inside the “channel” folder. The full source code of the Create component can be found here.
We can understand each channel as a CometChat group. Hence, when we are talking about we are creating a new channel, it means that we are also creating a new CometChat group. To create a new CometChat group, we use cometChat.createGroup(group). You can refer to the below code snippet for more information.
The <Main /> Component
The Main Component
The Main component is used to render the chatbox if the end-user has selected a text channel or start a direct call if the end-user has selected a voice channel. On the other hand, the Main component also contains the RightSidebar component. After the end-user has selected a channel, the RightSidebar component will render the list of members on that channel. It would help us increase the UX for the application. Aside from that, the Main component has the Header component, the Header component does not display anything, it is created to make the UI be more attractive. Please create the “Main.js” file inside the “server” folder. The full source code of the Main component can be found here.
As mentioned above, if the end-user has selected a voice channel, a direct call will be started. The CometChat service helps us to achieve that. For more information about direct calling, you can refer to this link.
The <Header /> Component
The Header Component
The Header does not display anything, it just has a border-bottom to make the UI be more attractive, it does not contain any business logic. Please create the “Header.js” file inside the “server” folder. The full source code can be found here.
The <RightSidebar /> Component
The RightSidebar Component
After the end-user has selected a channel, the RightSidebar component shows the list of members that belong to that channel. Please create the “RightSidebar.js” file inside the “server” folder. The full source code of the RightSidebar component can be found here.
To load the list of members that belong to the selected channel, we can use the CometChat service because the channel could be understood as a CometChat group as mentioned earlier. You can refer to the code snippet below. If you would like to know more about other CometChat services, you can refer to this link.
On the other hand, the RightSidebar component has to reload the list of members if there is any change in the channel such as adding a new member, remove the current member, and so on. Fortunately, the CometChat services are providing a way to listen to the group changes in real-time. You can refer to the below code snippet for more information. The best practice is to remove the group listener after the component has been unmounted. We can achieve that by calling cometChat.removeGroupListener(selectedChannel.guid);
To increase the UX in the application, we also need to show the online/offline status of each user in the channel. We can listen to user presence by using the CometChat service. You can refer to the below code snippet. Please do not forget remove the user listener by calling cometChat.removeUserListener(userPresenseListenerId); after the component has been unmounted.
The <Sidebar /> Component
The Sidebar Component
Each member will be rendered by using the Sidebar component. The Sidebar component accepts one props - the information of the member. Please create the “Sidebar.js” file inside the “server” folder. The full source code can be found here.
Wrapping Up
In conclusion, we have done an amazing job in developing a Discord clone by leveraging React.js, Firebase, CometChat SDK, and React UI Kit. You’ve been introduced to the chemistry behind Discord and how the CometChat SDK makes the Discord clone buildable.
You have seen how to build most of the chat functionalities such as real-time messaging using CometChat. I hope you enjoyed this tutorial and that you were able to successfully add mentions into your React chat app. Jump into the comments and let me know your experience.
About the Author
Hiep Le is a software engineer. He takes a huge interest in building software products and is a full-time software engineer. Most of his work is focused on one thing - to help people learn.
Hiep Le
CometChat