Discord Clone using ReactJS — The Written Guide for Beginners

Srikar Kusumanchi
Clever Programmer
Published in
31 min readOct 22, 2020

The Discord Clone by Clever Programmer was something everybody was looking forward to. People were anxiously waiting for this day to come as Discord is very popular and would look good on a resume. The day finally came and the Clever Programmer team dropped a live stream on YouTube. Now here comes the written guide.

This article will go in depth on how to make the Discord Clone using ReactJS. We will also use Firebase for user authentication, storage, and hosting. Firebase is a great tool to get your backend started up quickly. We will also be using HTML, CSS, and Redux. You can view the prerequisites to this clone below.

Throughout the article, we will be using the BEM CSS naming convention for naming classes. BEM stands for Block, Element, Modifier, and is simply a way to organize CSS code.

Prerequisites:

Visual Studio Code Extensions

To make your life way easier, install a Visual Studio Extension called ES7 React/Redux/Graph-QL/React-Native Snippets. This extension will help you by writing all of the boilerplate code for you. Also install an extension called Prettier, it helps format your code making it easier to read.

Visual Studio Code Settings

One final setting which is super helpful allows you to get emmet shortcuts working within the JSX section of each React component. Open VSC’s Settings with Command+, on Mac or Control+, on Windows. Search for emmet include, and choose Add Item. For the key, enter javascript, and the value, add javascriptreact, both all lowercase.

Emmet Include Languages

If you do not have an Add Item button, choose Edit in settings.json, and add the following code.

    "emmet.includeLanguages": {
"javascript": "javascriptreact"
}

Now that you’ve got the prerequisites let’s get going!!! 🔥 🚀

Open Visual Studio Code and open the terminal using Command+J (on Mac) or Control+J (on Windows). Now that you’ve opened it up, change directory into where you would like your clone to be stored. Once you’ve done that, we can start setting up the project.

Before, when doing this manually, it was a pain in you know what. Now Redux has created a template with a user-friendly guide on how to use it.

To use this neat template, type one of the following two commands in the terminal and press Enter. The command you use depends upon your situation: new or existing project.

For a new project, use these commands. Be sure to change the directory into the newly created discord-clone directory afterward.

npx create-react-app discord-clone --template redux
cd discord-clone

For an existing project, change the directory into the existing project, and use this command.

npm install @reduxjs/toolkit

This may take a while so sit back and relax, maybe get a cup of coffee if you want one. You know that it’s finished when you see “Happy Hacking” in the terminal. If you see this, you’re ready to move on. You should now have a folder layout like this.

New Template File List

Now that we have the React App installed, we can start it by typing the next command in the terminal.

npm start

If you did everything correctly, you should see a new tab in your browser window similar to the image below. If you don’t, open another tab and type this into the address bar.

http://localhost:3000

If that still doesn’t work, click here and see if it works now.

Now we want to clean up our folders by removing some files, so we can get started with the Discord Clone.

Delete these three files from the src folder: App.test.js, logo.svg, and setupTests.js. Now open the features folder and rename Counter.module.css to Counter.css. Then go to Counter.js and replace the last import statement with the line of code below.

import styles from "./Counter.css";

Your code in App.js should look something like this.

import React from "react";
import "./App.css";
function App() {
return (
<div className="app">
</div>
);
}
export default App;

Now in between the div tag, add this piece of code.

<h2>Let's Build a Discord Clone!!!</h2>

Remove all of the code in App.css. Now go to index.css and add this CSS snippet at the top of the file. What did you notice?

* {
margin: 0;
padding: 0;
}

If you noticed that there’s no margin or padding, then you are correct!

Open the features\counter folder, select the files Counter.css, Counter.js, and counterSlice.js, and move them from the counter folder to the features folder. After you’ve done that, delete the empty counter folder.

Firebase

Let’s set up Firebase for our project. First, go to console.firebase.google.com. Next, click “Add Project”, if you’re a new user it might say “Create a New Project” or something like that. It will look like this for existing users.

Once you click it, type “discord-clone” for the name of your project.

For the next step, you can enable Google Analytics if you would like, however, it’s not required. After you’ve selected your choices Firebase will start to create the project for you. Once it’s done it should look like this.

Once you get this, you can click “Continue”. Firebase will take you back to the project’s screen then click the button that I pointed an arrow to in the below image.

You will be asked for a name for the web application, you can put whatever you like. However, I just put “discord-clone”. Once you type in the name, click “Register App”.

It will ask you to add your Firebase SDK, but for now you can just click the blue button and go forward.

Go to the authentication tab on the sidebar and click on it.

Then click on the “Sign-in Method” tab and select “Google” and toggle “Enable”. When you’ve completed that click “Save”.

Now that you’ve setup authentication click the Firestore logo below the authentication logo. Click “Create database” once you’ve done so. Then choose “Start in test mode” and click “Next”. After that, click “Enable”.

We have successfully set up our project! Let’s start making the Discord Clone!

1. Sidebar

Under the h2 tag mentioned above, write <Sidebar />, this will cause an error. In order to prevent it make a file called Sidebar.js in the src folder. Let’s go into Sidebar.js and type “rfce”. You should get an option to autocomplete the snippets as shown in following picture.

Hit Enter while on “rfce”. This will autocomplete the React boiler plate for you. Now let’s change the class of the div element provided us to “home”. As I said at the beginning of the article, we follow the BEM CSS naming convention while styling our components.

The BEM CSS Naming Convention helps our CSS and JSX code stay organized for us to read later, and everything becomes easy to keep track of.

Let add some text for now to show how it works, let’s say “Hello, I am the sidebar” and also add an import statement at the top of the file. The code in Sidebar.js should be the following.

import React from "react";
import "./Sidebar.css";
function Sidebar() { return (
<div className="sidebar">
<h2>Hello, I am the sidebar</h2>
</div>;
);
}export default Sidebar;

To create the section boxed here, we have to create another div inside the current div with a class of “sidebar__top”. Inside that div, there should be an h3 with Clever Programmer inside of it. Now your code should look like this.

import React from "react";
import "./Sidebar.css";
function Sidebar() { return (

<div className="sidebar">
<h2>Hello, I am the sidebar</h2> <div className="sidebar__top"> <h3>Clever Programmer</h3> </div> </div> );
}
export default Sidebar;

In order to get the icons you see above, we need to install Material-UI. To install Material-UI, open the terminal, type the two commands below, and hit Enter.

npm install @material-ui/corenpm install @material-ui/icons

Now in the Sidebar.js file, type “import ExpandMoreIcon from “@material-ui/icons/ExpandMore”;” and then type “<ExpandMoreIcon />” after the h3 tag that says “Clever Programmer”. The code should look like this.

import React from "react";
import "./Sidebar.css";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
function Sidebar() {return (
<div className="sidebar">
<h2>Hello, I am the sidebar</h2>
<div className="sidebar__top">
<h3>Clever Programmer</h3>
<ExpandMoreIcon />
</div>
</div>
);}export default Sidebar;

Let’s create the CSS file for this, so in the src folder create a new file named Sidebar.css. Inside Sidebar.css, add a display of flex, flex direction of column, a flex of 0.25, a height of 100vh, and a background color of #2f3135 to the sidebar class. Then add a display of flex, justify content space between, align-items center, a padding of 20px, a background color of #2f3135, color of white, and a bottom border of 3px solid #26282c. Your CSS should look like the below.

.sidebar {  display: flex;  flex-direction: column;  flex: 0.25;  height: 100vh;  background-color: #2f3135;}.sidebar__top {  display: flex;  justify-content: space-between;  align-items: center;  padding: 20px;  background-color: #2f3135;  color: white;  border-bottom: 3px solid #26282c;}

Now remove the h2 tag in Sidebar.js since we don’t need it anymore. Add another div in Sidebar.js with a class of “sidebar__channels”. Inside that, add another div with a class of “sidebar__channelsHeader” and inside that add another div with a class of “sidebar__header”. Inside “sidebar__header”, type “<ExpandMoreIcon />” and below that type the following.

<h4>Text Channels</h4>

Outside of the “sidebar__header” div type AddIcon with a class of “sidebar__addChannel” and import it from Material-UI.

Your Sidebar.js code should look like the following.

import React from "react";
import "./Sidebar.css";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import AddIcon from "@material-ui/icons/Add";
function Sidebar() { return (
<div className="sidebar">
<div className="sidebar__top">
<h3>Clever Programmer</h3>
<ExpandMoreIcon />
</div>
<div className="sidebar__channels">
<div className="sidebar__channelsHeader">
<div className="sidebar__header">
<ExpandMoreIcon />
<h4>Text Channels</h4>
</div>

<AddIcon onClick={handleAddChannel} className="sidebar__addChannel" />
</div>
</div> </div> );}export default Sidebar;

Now go to Sidebar.css and add styling for sidebar__channels, sidebar__addChannel, sidebar__chanelsHeader, and sidebar__header. For sidebar__channels add a styling of flex: 1, and for sidebar__addChannel add cursor: pointer. sidebar__addChannel also has a hover effect of color: white. Then sidebar__channelsHeader has display of flex, justify-content: space-between, align-items: center, a padding of 10px, a background color of #2f3135, and a color of gray. sidebar__header should have a display of flex and align-items: center.

Your Sidebar.css code should look like the following now.

.sidebar {  display: flex;  flex-direction: column;  flex: 0.25;  height: 100vh;  background-color: #2f3135;}.sidebar__top {  display: flex;  justify-content: space-between;  align-items: center;  padding: 20px;  background-color: #2f3135;  color: white;  border-bottom: 3px solid #26282c;}.sidebar__channels {  flex: 1;}.sidebar__addChannel {  cursor: pointer;}.sidebar__addChannel:hover {  color: white;}.sidebar__channelsHeader {  display: flex;  justify-content: space-between;  align-items: center;  padding: 10px;  background-color: #2f3135;  color: gray;}.sidebar__header {  display: flex;  align-items: center;}

Going back to Sidebar.js, we need a div with a class of “sidebar__channelsList”. This is where we will be creating the list of channels the user is in. Under this div, we will type “<SidebarChannel />” and create two new files called SidebarChannel.js, and SidebarChannel.css. Open your newly created SidebarChannel.js file and type the quick command “rfce” and type “import ./SidebarChannel.css;” to import your CSS file. Create div with a className of “sidebarChannel” and under it create and h4 and a span tag. In the span tag, add a className of “sidebarChannel__hash” and add a “#”. Outside of the span tag type what channel name you would like, I will be writing “YouTube”.

Your SidebarChannel.js should look something like this now.

Sidebar.js should look like this.

Let’s style the Sidebar Channel now. Open the SidebarChannel.css file that we created above. We are going to create a hover effect for the h4 element in sidebarChannel by targeting it. The styles we’ll be adding are “color: white;” and “background-color: #40464b”. Let’s target the h4 by typing “.sidebarChannel > h4” and add “display: flex”, “padding-left: 15px”, “align-items: center”, “background-color: #2f3135”, “color: gray”, and “cursor: pointer”. Now let’s style the little hashtag so add a font-size of 30 pixels and a padding of 8 pixels to the “sidebarChannel__hash” class.

Once you save these changes, you can see the immediate CSS effects in place. Now let’s work on the bottom section of the sidebar. There will be a part where it says, “Voice Connected”, but don’t worry we are not trying to trick you.

In order to start building the bottom section we have to get out of the “sidebarChannels” div and create a new div with a className of “sidebar__voice”. Now in order to get a network icon we need to use Material-UI, so let’s use that by adding this piece of code under the sidebar voice div.

<SignalCellularAltIcon className="sidebar__voiceIcon" fontSize="large" />

We also need to import this at the top so it actually displays on the website.

import SignalCellularAltIcon from "@material-ui/icons/SignalCellularAlt;

Under that we are going to create div with a className of “sidebar__voiceInfo” and inside this div we are going to have an h3 that says “Voice Connected”. This was the part where I was saying to not worry, so if you are. Don’t. And add a “p” tag that says “Stream”. Did you notice something?

The icon and the text is at the bottom of the page, so I will now be showing you how we can get that higher up in the website. First, let’s create another div called “sidebar__voiceIcons”. I know there are a lot of divs, but there are so many more to come. Under this div, add “<InfoOutlinedIcon />” and“<CallIcon />”. Like before, we need to import these to see some changes, so let’s import them at the top with these two lines of code.

import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined";
import CallIcon from "@material-ui/icons/Call";

Now going into Sidebar.css, add styling for sidebar__voice and add “display: flex;”, “justify-content: space-between”, “align-items: center”, “color: gray”, “background-color: #2f3135”, “padding: 10px”, and “border-top: 1px solid gray”. It’s all at the bottom right now, but we will start to make that look a little more clean. Add styling to “sidebar__voiceInfo” using “flex: 1” and a padding of 10 pixels. Make the voice icon green by adding a “color: #4b185” and add that same color the h3 inside of “sidebar__voiceInfo”. Let’s target the p tag and add “font-size: smaller”. Now we’re going to to add a padding of 10 pixels to the icons by adding it to the “.sidebar__voiceIcons > MuiSvgIcon-root” class.

This is how the Sidebar.css code should look like now.

Getting out of the sidebar__voice div, create a new div with a className of “sidebar__profile”. Inside of this, we are going to add an Avatar self-closing tag to use for profile pictures and a div with a class of “sidebar__profileInfo” and add an h3 with a username you would like. Also, don’t forget to import Avatar from Material-UI. Under that, add a p tag that says “#ID”. We will replace this later with an actual ID given by Firebase.

Outside of the sidebar__profileInfo div, we are going to have another div with a className of “sidebar__profileIcons”. Inside this div, we are going to add three more Material-UI icons.

<MicIcon />
<HeadsetIcon />
<SettingsIcon />

Sidebar.js should look something like this now.

We need to add the same styling of sidebar__voice to sidebar__profile so what we can do is write “, sidebar__profile” to add that same CSS. Add styling to sidebar__profileInfo by adding “flex: 1” and padding of 10 pixels. We are now going to target some classes, so add a color of white to the h3 tag inside sidebar__profileInfo and add a padding of 10 pixels to MuiSvgIcon-root class inside sidebar__profileIcons. You have now successfully added all of the CSS needed for the sidebar and it should look like the below now.

2. Chat

Now we’re going to build the Chat component. In VSC, create a file called Chat.js. In App.js, create a <Chat /> self closing tag. Don’t forget to import Chat.js into App.js, otherwise it won’t work. We are going to style App.css first, so add a display of flex to the app class. Did you notice something?

It now actually moves the sidebar to the initial 25% we set before. Going into Chat.js now, type the magic command “rfce to get the boilerplate. Add a className of “chat” to the parent div and add the below import statement.

import "./Chat.css";

After you’ve added the above import statement, create a file named Chat.css. Inside Chat.js, add an h2 saying “Chat”. Let’s work on the chat header now on the chat screen.

Chat Header

Let’s create a ChatHeader component inside Chat.js by adding a self-closing Chat Header component tag and create a file called ChatHeader.js. Don’t forget to import ChatHeader.js in Chat.js. Inside this file, type “rfce” and add a className of “chatHeader”. Next, create a file called ChatHeader.css and import that into ChatHeader.js. You can do a sanity check by adding and h3 tag inside of ChatHeader.js saying “Header”. You can now take out the h2 saying “Chat” in Chat.js.

Now we’re going to create two divs, one with chat header right and the other, chat header left. Let’s get started by creating a div called “chatHeader__left” and another one called “chatHeader__right”. Under chatHeader__left add an h3, within that h3, add a span tag with a class name of chatHeader__hash. Inside the span tag, add a #. Outside the span, we will be using “Test Channel Name”, but we will be using props soon and that will go ahead and help you out. We’ll style this in just a sec.

Under the div chatHeader__right, we are going to have three icons: “NotificationsIcon”, “EditLocationRoundedIcon”, and “PeopleAltRoundedIcon”. Add these import statements and self closing tags below.

import NotificationsIcon from "@material-ui/icons/Notifications";
import EditLocationRoundedIcon from "@material-ui/icons/EditLocationRounded";
import PeopleAltRoundedIcon from "@material-ui/icons/PeopleAltRounded";
<NotificationsIcon />
<EditLocationRoundedIcon />
<PeopleAltRoundedIcon />

We are going to create a search field by adding another div with a class of “chatHeader__search” and inside that, we are going to add an input tag with a placeholder of “Search”. We are also going to add a Material-UI icon by adding this self-closing tag and import statement.

import SearchRoundedIcon from "@material-ui/icons/SearchRounded";<SearchRoundedIcon />

Outside this div, we will be adding two more icons a send icon and help icon and we need to import these from Material-UI, too.

import SendRoundedIcon from "@material-ui/icons/SendRounded";
import HelpRoundedIcon from "@material-ui/icons/HelpRounded";

<SendRoundedIcon />
<HelpRoundedIcon />

This is how your ChatHeader.js file should look right now.

If you have OCD, you might be frustrated about how messy it looks, but we will fix it right now by going into ChatHeader.css and deleting the the first h2 tag in Chat.js. Inside ChatHeader.css, add a style for chatHeader by typing “display: flex”, “justify-content: space-between”, “align-items: center”, “color: gray”, and a padding of 10 pixels. Now let’s target the hash by adding style to “chatHeader__hash”. The styles we will be adding are: “color: gray”, “font-size: 30px”, and a padding of 10 pixels. We need to target the h3 now, let’s do that by creating some styles for the h3 inside of “chatHeader__left”. The styles are: “display: flex”, “align-items: center”, and “color: white”. You might not be able to see it, but that’s fine because we set the color of the text to white. Let’s style chatHeader__right now.

In chatHeader__right, we need the following styles: “display: flex”, “align-items: center”, “flex: 0.5”, and “justify-content: space-between”. Now we need to target the icons itself by adding a padding of 5 pixels and “cursor: pointer” to “.chatHeader__right > .MuiSvgIcon-root”. Let’s create a hover effect for it by changing the color to white.

Now let’s style the search bar since that’s a little messed up. The styles we will be adding are: “display: flex,” “align-items: center”, “color: gray”, “background-color: #2f3135”, “border-radius: 3px”, and “padding: 3px”. Let’s target the input now by adding: “background: transparent”, “outline-width: 0”, “color: white”, “border: none”.

Your ChatHeader.css should look similar to this.

Going into Chat.css, let’s add a display of flex, flex-direction of column, flex of 0.75 to take up the remainder of space, background-color of #363a3f, height of 100vh.

.chat {  display: flex;  flex-direction: column;  flex: 0.75;  background-color: #363a3f;  height: 100vh;}

Go into Chat.js now, create two new divs one with a class name of “chat__messages” and the other one “chat__input”. Let’s build the input first then we can build the messages part. Inside chat__input, we are going to add a few Material-UI icons, but first we will add a circle icon with the following import statement and self-closing tag.

import AddCircleIcon from "@material-ui/icons/AddCircle";
<AddCircleIcon fontSize="large" />

Below that, we are going to create a form with an input and the placeholder should be “Message #TESTCHANNEL”. We are going to create a button with the type being “submit” and inside of it type “Send Message”. We are going to give a class name of chat__inputButton to style it later. We are going to create another div under it called chat__inputIcons and put three icons in there. Don’t forget their import statements.

import CardGiftcardIcon from "@material-ui/icons/CardGiftcard";
import GifIcon from "@material-ui/icons/Gif";
import EmojiEmotionsIcon from "@material-ui/icons/EmojiEmotions";

<CardGiftcardIcon fontSize="large" />
<GifIcon fontSize="large" />
<EmojiEmotionsIcon fontSize="large" />

It probably looks really ugly right now and not what you’re expecting. Let’s go to Chat.css to fix up some of these issues by starting with chat__input. Inside chat__input, the styles that we are going to add are “color: lightgray”, “display: flex”, “align-items:center”, “justify-content: space-between”, “padding: 15px”, “border-radius: 5px”, “margin: 20px”, “border-top: 1px solid gray”, and“background-color: #474b53”. Let’s now target the input tag inside of the form by adding: “padding: 15px”, “background: transparent”, “border: none”, “width: 100%”, “outline-width: 0”, “color: white”, “font-size: large”. We need to take up most of the space, so we need to target the form tag and add a flex of 1.

Now we don’t really want to display the button because Discord doesn’t have a button and to send messages you hit Enter. In order to make this change, we have to add styling to the chat__inputButton class which will be “display: none”. The icons are little squished so let’s give them some breathing room by targeting the icons an giving them a padding of 5px. Now we want the rest of the screen to be taken up by the messages and with Flexbox, which is what we are using, we can use flex: 1 to solve this problem. To prevent overflow of your messages, add these lines of code.

.chat__messages {flex: 1;overflow: scroll;}.chat__messages::-webkit-scrollbar {display: none;}.chat__messages {-ms-overflow-style: none;scrollbar-width: none;}

Your final Chat.css should look like this.

Now we are going to have a Message component to render the messages on the screen. First, you have to add a self-closing tag with “Message” inside of it. Secondly, create a file called Message.js and Message.css. Inside of Message.js, type rfce and import Message.css. Add a className of “message” to the parent div and add an Avatar tag, like before with the ChatSidebar. After that, create a new div with a className of “message__info” and add an h4 inside of it with some username, however we will fill this in later with Firebase. Inside of the h4 tag, create a span tag with the className of “message__timestamp” and inside of it type “Timestamp”. We will also use Firebase to fill it in with real time. Now create a p tag outside of the h4 tag and type Message inside of it acting as a placeholder. Import Message.js in Chat.js in order to make this appear. Let’s style Message.css in order to make things look a little bit more beautiful.

We are now going to target the message class by adding: “display: flex”, “align-items: center”, “padding: 20px”, and “color: white”. Now let’s target the message__info to get some spacing in between the avatar and title by adding “margin-left: 20px”. We need to change the timestamp up a little so let’s target that class which is “message__timestamp” and add “color: gray”, “margin-left: 20px”, and “font-size: x-small” to make the timestamp smaller than the username. Now you’re blank CSS file should look something like this.

Let’s start integrating some Firebase. 🔥 🚀

3. Firebase Setup

Now go to console.firebase.google.com and open your Discord Clone project. Then, click the gear icon on the sidebar and click “Project Settings”.

Once you click on that, scroll down to the bottom and you’ll see something that says “Firebase SDK Snippet”. Select “Config” and copy all of the code inside there.

Go back into Visual Studio Code and make a new file called firebase.js and paste the code from Firebase inside there. Pull up the terminal, and type the below to install Firebase into your local project.

npm i firebase

Inside firebase.js, type “import firebase from firebase;” at the top of the file. Now we need to do some configuration inside of the file. The function initializeApp will create the firebase app and firestore enables you to write to the database. Auth enables the authentication so when a user logs in it adds it to Firebase. Provider enables you to sign in using your Google Account. After that we are going to explicitly export auth and provider and default export db. If you don’t know the difference between explicitly exporting and default exporting, it’s fine because it doesn’t really matter right now. Your firebase.js should look something like this now.

4. Redux Integration

Let’s integrate Redux now by going into store.js and replacing everything that says or contains “counter” to “user”. We need to rename counterSlice.js to userSlice.js and inside of userSlice.js change everything from “counter” to “user”. We are going to change “value: 0” to “user: null”, if you don’t understand it doesn’t matter because right now you need to focus on pattern recognition instead of figuring out how it fully works. We are going to now remove “decrement” and “incrementByAmount”. We are going to create a function called “login” by replacing “increment” with it. It takes another parameter called action and there’s going to be another function called logout. Now you don’t really need to type anything here if you don’t understand Redux. Just read and try to understand. The function logout takes a parameter, state, and we set the user back to null. Get rid of all the comments in the login function and do “state.user += action.payload;”. The part where there’s “userSlice.actions” you can remove what’s inside the curly braces and type “login, logout”, instead. You can remove the async function and the comments on top of it. You can also remove the comments above selectCount.

A selector is basically how we can go ahead and grab this information once it’s done, in simple terms. We are going to replace “selectCount” with “selectUser” and instead of “state.user.value”, we are going to use “state.user.user”. Your userSlice.js. should look like this now.

Now I am going to explain how Redux is used in this clone.

Redux Explanation

Let’s say you have an app and inside that app you have some components like chat and header. Then from the chat component there’s a message component that branches off. Let’s say we have a login component and our app connects to this component. Imagine we had a user, a piece of state, and we set the user at the login component and get the user’s information. If I want to get the user somewhere, say the message component, then we would have to send the user to the app, then to the chat component as a prop, from there we have to pass it as a prop to the message component. It starts to get really messy because you would have to keep passing it through to everywhere you need it. This complexity of passing down things to components is called Prop Drilling and should be avoided when creating projects.

To avoid all of this, we are going to introduce something like a data layer, so imagine an onion and this is the data layer or Redux. The data layer in this case has multiple slices inside of it kind of like how an onion has slices. So here there’s going to be a user slice which contains all the information relating to the user. Then we are also going to have an app slice which contains generic information about the app like what channel are you in, however, we could call it channel slice but we are just going to call it app slice.

The benefit of doing this is once we get the user from the login component, we can push it into the user slice and wherever I need it I can just pull it out because the layer is surrounding the entire app. We can do the same thing for other spots in the app, too. You don’t want to put everything in one slice because you want your code to be organized and clean. The store is essentially the data layer and the reducer is something which listens to an action. An action is for example, pushing the user into the data layer. I attached a diagram to show the flow of how Redux works. If you still don’t understand Redux, I suggest you check our blog on Redux for further clarification.

We are also going to create an app slice, so go to store.js and add this piece of code under the user.

app: appReducer,

By the end, your store.js should look like this.

We are going to now delete Counter.js and Counter.css after that we are going to create a file called appSlice.js. In appSlice.js, copy everything from userSlice.js and replace anything that contains “user” with “app”. Inside the “initialState”, add these two lines of code.

channelId: null,
channelName: null,

In the reducers, we are going to have “setChannelId” instead of “login”. We can also remove the logout function and for the actions remove everything inside the curly braces and type “setChannelId”. We are going to have two selectors in app slice, one for the channel id and the other for the channel name.

export const selectChannelId = (state) => state.app.channelId;export const selectChannelName = (state) => state.app.channelName;

5. Login/Logout

Now we’re going to create a login page and we need to pull in the user into App.js. To do this, we need to type this above the return in App.js.

const user = useSelector(selectUser);

useSelector is a React hook and putting selectUser as a piece of state will retrieve that piece of information. We need to import selectUser first from the userSlice in order to make this work.

import { selectUser } from "./features/userSlice";

We want to online render the app once the user is logged in right? So in order to do that we have to use something called the ternary operator which basically simplifies the if and else conditional statement to one line. We are also going to create a Login component so the user can login using their Google account. Create a file called Login.js and another file called Login.css to style the login page.

Inside of Login.js, type rfce and import the newly created Login.css. Give the parent div a className of “login” and create and h2 inside of it saying “Login”. It won’t render the chat stuff right now because we don’t have a user set currently. We are going to create a div that has a className of “login__logo” and it’s going to have an image tag inside of it. Here is the link to the image: https://upload.wikimedia.org/wikipedia/sco/thumb/9/98/Discord_logo.svg/800px-Discord_logo.svg.png.

We are also going to create a button which is a Material-UI button, so the b in the button tag has to be capitalized. Inside of the Button tag, type “Sign In”. Inside of the opening tag, write “onClick={signIn}”, this command will fire off a sign in function, we will create later, when the user clicks the button. Don’t forget to import this from “@material-ui/core”.

Let’s put a placeholder in the signIn function for now but we will come back to it later. For now, create an arrow function with signIn as the function name and inside of it type “console.log(‘You clicked the sign In button’).” You can also remove the h2 right now. Let’s style this before we go any further.

Inside Login.css, we are going to style the login by adding the following: “display: grid”, “place-items: center”, “height: 100vh”, and “width: 100%”.

We are going to target the button and add a width of 300 pixels, a background color of #738adb, color of #eff2f5, and a font-weight of 800. We are also going to create a hover effect with setting the background-color: black and setting the color to the background-color we set earlier. Now let’s target the image and make it a little bit better by adding: “object-fit:contain” and “height: 150px”. Login.css should look like this.

Integrating Google Login

Let’s go back to Login.js and remove the console.log. Instead we are going to import auth and provider from our local firebase file and type the following inside of the signIn function.

auth.signInWithPopup(provider).catch((error) => alert(error.message));

We are going to catch the error only because we don’t really care if they successfully logged in. You might be thinking “Seriously, this is it…this is what we have to do for Google Login. It can’t be this easy.” It actually is this easy because Firebase handles the rest behind the scenes and all we have to do is write this one liner. If we do login, it’s not going to redirect us and that’s fine because we didn’t set that yet. We will be fixing that issue right now.

Let’s go back to App.js and we are going to use a useEffect hook. useEffect is a React hook that fires the code only once when the component loads. We use a useEffect here because we wouldn’t want to be running in an infinite loop. We need to import useEffect from React and import auth from our local firebase. We are going to use onAuthStateChanged here because whenever a user logs in, it will go ahead and get the user. Anytime the authentication state changes, we want to listen to them. In order to do this, we have to check if there’s a user logged in and we want to log the user in right. In the userSlice, we declared a login function, so we are going to use that.

We need to import some things now. First is dispatch, dispatch allows us to push things into the data layer so we can retrieve where ever we feel like. To use dispatch, we need to import the and use the following statement.

import { useDispatch, useSelector } from "react-redux";const dispatch = useDispatch();

Inside of the if statement, we need to dispatch login and import that from the userSlice. We need to set some details in order when doing this. First, we need to set the uid to authUser.uid and photo to authUser.photoURL. Secondly, we need to set the email to authUser.email and displayName to authUser.displayName. Inside of the else, we need to dispatch the logout and to do this we need to import this from the userSlice. We also need to set our dependency to be the dispatch. App.js should look something like this now.

Logout

Now we need to implement logout, you’re probably thinking this is going to be super hard, but it isn’t. It’s actually an easy task to do, so let’s get to it. What we want to do is if we click our profile image on the sidebar then it should log us out. Let’s go to Sidebar.js and go to the Avatar tag. We actually want to use the real profile picture so let’s add that. To do this, we need to use a selector to pull that data from the data layer. We need to import useSelector from “react-redux” and import selectUser from the userSlice.

const user = useSelector(selectUser);

We are going to change the source of the Avatar to user.photo.

<Avatar src={user.photo} />

We are also going to replace the user name with user.displayName and change the ID, too.

<h3>{user.displayName}</h3>
<p>#{user.uid.substring(0, 5)}</p>

Substring is a method that in this case only gets the first five characters. The first number is inclusive and the last number is exclusive.

Here’s how simple it is to logout. So we are going to add an onClick event handler to the Avatar and the function will be auth.signOut. We need to import auth from our local firebase file. That’s it.

<Avatar onClick={() => auth.signOut()} src={user.photo} />

Now when we click the profile image, Redux will get rid of the user in the data layer since we logged out. It also redirects us to the login page.

6. Integrating Firebase

Channel functionality

We are now going to start replacing the hard-coded channel names with channel names from Firebase. Go to Sidebar.js, we are going to create a piece of state called channels and setChannels. I am going to say useState, another React hook, and feed it a blank array.

const [channels, setChannels] = useState([]);

Let’s create a useEffect that fires off when the sidebar component loads. We are going to import the db from our local firebase file. From there, are going to go inside our channels collection and take a snapshot of the database. We take a snapshot so whenever we add a channel it will retrieve the latest information in real time. I recommend that you watch or read some the clones that Clever Programmer built in order to understand snapshot or you can click here to go to a video that Clever Programmer published on Firebase snapshot. For each doc, we are going to return an object and this object will have an id and the channel name.

Replace the SidebarChannel tag with the below code.

{channels.map(({ id, channel }) => (    <SidebarChannel    key={id}    id={id}    channelName={channel.channelName}   />))}

It’s fine if you see no channels, once you save it because it will be pulling the channel names from the database. We are now going to add a function that adds a channel when you click the plus button. We are going to add this function below the useEffect and it’s going to be an arrow function. Inside the function, we are going to prompt the user for a channel name. We are going to use an if statement to protect us against an undefined error. We are going into the db, inside the channel collection, and add the channelName. Pass in the id and channel into the map function. If you got an error, check the code below and see what mistake you made.

Inside of SidebarChannel.js, change the “channel” prop to “channelName”. Replace the hard-coded channel name with “{channelName}”. If you try to set a channel name right now it should work. When we click on the channel name we want Redux to update the store. We need to declare dispatch in order to do this.

const dispatch = useDispatch();

In the div, create an onClick with setChannelInfo inside of it. Go into appSlice.js and change “setChannelId” to “setChannelInfo”. Replace the “state.app” with the below. Don’t forget to export it.

state.channelId = action.payload.channelId;
state.channelName = action.payload.channelName;

Your appSlice.js should look like the below.

Go into SidebarChannel.js and change“ setChannelId” to “setChannelInfo”. We need to dispatch the id and channel name in SidebarChannel.js, so we can update the channel name at the top when we visit different channels. It should look like the below.

Dynamic Channel Name

Let’s go into Chat.js, so when we click on a certain channel, the channel name will change dynamically. We need to create some variables in this file to get some information to go ahead using React’s useSelector and we have to import it from “react-redux”.

const user = useSelector(selectUser); 
const channelId = useSelector(selectChannelId);
const channelName = useSelector(selectChannelName);

Here are the import statements needed.

import { useSelector } from "react-redux";
import { selectChannelId, selectChannelName } from "./features/appSlice";
import { selectUser } from "./features/userSlice";

We need to put channelName as a prop for chatHeader, so we need to replace <ChatHeader /> with the following.

<ChatHeader channelName={channelName} />

Now let’s go into ChatHeader.js, put channelName as a prop and instead of the random channel name you put, type the below.

{channelName}

It now changes the channel name dynamically when we click on a specific channel.

Chat feature in real-time

First, we need to collect the input, so in order to do that we need to go into Chat.js and create a piece of state called input.

const [input, setInput] = useState("");

To hook this up, we have to go to the input tag and add value is equal to input. We also need to import useState. Another thing we need to add to the input tag is an onChange which takes an event sets the input to the value of the text box. We also need to disable the input if we haven’t selected a channel. Type “disabled={!channelId}” for both the input and button. For the input field, we can change the placeholder with `Message #${channelName}`.

Dynamically changing the messages

We need to set the messages by declaring the messages and setMessages which equals useState and pass a blank array inside of it.

const [messages, setMessages] = useState([]);

Instead of hard-coding the messages, we need to map through the messages, so that with every message we render out a message component. It’s fine if you don’t see any messages right now. We need to create a useEffect now to retrieve the messages from Firebase. To do this, access the database and the collection called channels. From there, obtain the document and pass in the channelId and get the collection of messages. Order the messages by the descending time which means the latest messages will be at the top. Let’s take a snapshot and for each snapshot, we want to set the messages with snapshot.docs.map((doc) => doc.data()). We are going to put this if there’s a channelId. In order for the page not to refresh when we submit the message we have to add this piece of code.

const sendMessage = (e) => {
e.preventDefault();
};

We need to now hook this up with the button, so when we click enter it will send the message. We have type this snippet in the button, so that works.

onClick={sendMessage}

We are going to add some neat Firebase stuff now. First we need to go into the collection, then the document, and then the collection. After that, we need to add the message, user, and timestamp. We need to import firebase from the actual firebase.

import firebase from "firebase";

We are going to use the serverTimestamp for the timestamp because that will be the same time regardless of where you live. We need to also set the input to be blank after we send the message.

The useEffect needs a dependency of channelId since we use it in there. We are going to add the following three props in the Message component.

timestamp={message.timestamp}message={message.message}user={message.user}

Go into Message.js and add the following props: timestamp, user, message. Now the Avatar source can be user.photo. We can replace the hard-coded display name with user.displayName and replace the timestamp with new Date(timestamp?.toDate()).toUTCString()}. Also replace the hard-coded message with message.

Your JS files should look like the following:

7. Deployment

Congratulations, you have built the Discord clone, however, we need to deploy this, so you can go brag to your friends and family. In order to do this, we need to open a new terminal and type the below.

firebase init

When you run this, we need to select hosting and click enter. After that we select “Use an existing project”, and select the discord clone we made right now. We need to change the public directory to build instead because React is going to create the static page for you in a folder called build. Then type y or yes and that’s it. You’re done initializing Firebase deployment.

We need to now create an optimized build and to do that we need to type the below.

npm run build

This command allows us to create an optimized production build, so it compacts all the JavaScript files and makes it a static site. It will be compacting this in a folder called build. That’s why we set the folder to build instead.

Final step to deploy this website and show it off.

firebase deploy

After this is done running, it will give you a link to which you can visit your website. LET’S GOOOO!!! 🔥 🚀

8. Done with this. Now what?

The fun doesn’t end here, it still continues. You can keep making new things and learn new things with the other blogs that Clever Programmer has published.

There are many more clones, tips, and so much more to come. I know you’re hyped and ready, but you have to wait.

Share this with anybody you think would benefit from this. Have any suggestions? Feel free to hit me up!

Thanks

Srikar Kusumanchi

--

--

Srikar Kusumanchi
Clever Programmer

Blogger, freelancer, web/mobile app developer! Let’s connect! IG: srikar.programs