Creating Your Own Chat Room with React, Node, Socket.io and the Cloud: Part 2

Bradston Henry - May 7 '21 - - Dev Community

In this blog, we will complete everything we need to get our Chat Room up in running.

Sample Chat Gif

In part 1, we focused on creating the socket.io NodeJs Server that would manage our chat. Its primary purpose was to receive messages from users in our Chat room and to send it to other Chat Room users in in real time.

In part 2, we will be focusing primarily on the ReactJS front-end portion of the Chat room where users will view messages and send messages to other users and connecting it to our server.

NOTE: If you would like to follow along and build the application with me, I recommend you go back to part 1 and complete that portion before moving any further. The ReactJS portion of our application will not be able to work without a server to interact with.

To make our lives easier, I have created the base ReactJS application that we will be using to house our Chat Room. this includes the UI and visuals as well as some starter code that will allow us to code in what we need to connect to our Node server. To get the starter code, download the source from my github.

So here is a brief overview of what we will cover is this particular blog to get the React portion of the application up and running:

So here is quick overview of what we will be doing with our Node server:

  1. Install and configure our React application to use socket.io

  2. Implement Socket methods to allow our React application to communicate with our server( NodeJS application)

  3. Locally Test our React applications connection to our Node Server

  4. Deploy our ReactJS application to the cloud so it can be accessible to users from anywhere.

As you may have noticed, a lot of what we will be doing in this blog will parallel what we did in the last blog. This portion of development should be a bit faster as we should now have a base understanding of how socket.io works from the previous blog.

So without further adieu, let's get started...

Installing and Configuring Socket.io with ReactJS

So the first thing we are going to do, is navigate to our base ReactJS code using the terminal or command line. Our code should look something like this:



cd <you-path-directory>/simple-react-chat-app


Enter fullscreen mode Exit fullscreen mode

Once we have navigated to our source code, we are going to install all of our base dependencies listed in our package.json file using this command:



npm install


Enter fullscreen mode Exit fullscreen mode

Once we have installed all of the base frameworks and dependencies we need to run our application, it's time for us to install socket.io into our application. We will be installing the socket framework almost exactly how we did it on the NodeJS server except that we will be using a different library to get access to socket.io in our client application. Here is what we will run to install socket.io in React:



npm install socket.io-client --save


Enter fullscreen mode Exit fullscreen mode

As you have probably noticed, this is the "client" version of the socket.io. If you are interested in learning more about the client version of socket.io check out this link: socket.io Client documentation

Our next step will be to create a file that will be used to manage our client socket.io connection with our server. For ease, I have already created the file for you but it is currently blank. We will be adding the necessary code in the next few steps.

First, open the folder titled "src" in our React app source code directory and the open the folder titled "services". In the "services" folder, you will find a file titled "socket.js". We will be inserting all necessary socket.io connection code in this file. Open this file and add this code:



import socketIOClient from "socket.io-client";

const serverEndpoint = "<your-nodejs-server-url>";

export const socket = socketIOClient(serverEndpoint, {
    transports: ['websocket']
});


Enter fullscreen mode Exit fullscreen mode

In the first line we are importing are socket.io-client library into our file for use.

In the next line of code, we are setting the serverEndpoint to our NodeJs server. This is the string value of the url that your Node server is currently hosted. For example, my serverEndpoint line would look like this:



const serverEndpoint = "simple-node-chat-server-bsh.us-south.cf.appdomain.cloud";


Enter fullscreen mode Exit fullscreen mode

Our next line is actually initializing the connection between our client and our server. We are also specifying in this line of code a transport parameter. Essentially, what that is doing is setting the connection protocol we would like to use in our socket connection to our server. If you would like to learn more about how transports work and the the different typed of connection protocols, check out this link.

We now have all the code we need to connect to our socket.io NodeJs server. The reason we are creating a separate file to manage this connection, is because now if we desire to expand this application in the future, we have one location that handles this connection. We can then easily import this file into any React Component or file that we would like to use our socket.io connection with.

With that, we have set up our React application to connect to our NodeJS server. Our next step will be to implement the socket.io methods we need to communicate appropriately with our server.

Implementing Socket Methods in ReactJS client

Since we already have everything we need setup on our server, our next job is to implement the correct methods to communicate with our server.

In order to do that, we will need to modify the code in our ChatRoom.js file. In our source code directory, open the 'src' folder and then open the "pages" folder. In the "pages" folder, open up the file titled "chatRoom.js". As the title of the file suggests, this holds all relevant code we need to operate our chat room.

As you will notice in the file, there is quite a bit of code already implemented, but we need to implement some new code to get our socket methods working.

At the top of the file directly under our last imported file we are going to import our "socket.js" file from our services folder. Your code should look something like this:



...
import ChatNotification from '../components/ChatNotification'

//Add socket import here
import {socket} from '../services/socket' 

let styles = {
....


Enter fullscreen mode Exit fullscreen mode

Now that we have our socket service imported, our first step is to setup the socket.io method that manages creating and setting our user data/identity. In order to do this we need to implement a socket.on method and socket.emit method.

As mentioned in part 1 of this blog, socket.on methods act as listeners, always looking out for messages and socket.emit methods act as messengers, sending messages to listening socket.on methods. In this case, socket.on methods are listening for messages from the Node server and socket.emit methods are sending messages to the Node server.

In our ChatRoom.js file, in our componentDidMount() method, we will be implementing our code. Within the if(!userIDVal){ we will be adding new code that looks like this:



if(!userIDVal){

   socket.on("SetUserData", userData => {
      //When user creation on server is complete, retrieve and save data to local storage
      localStorage.setItem('userID', userData.userID)
      localStorage.setItem('username', userData.username)
            console.log(userData)

      this.setState({currentUsername: userData.username, currentUserID: userData.userID})

      //Notify Socket server is not ready to chat
      socket.emit("UserEnteredRoom", userData)
   });

   //Send Socket command to create user info for current user
   socket.emit("CreateUserData")
} 


Enter fullscreen mode Exit fullscreen mode

So what exactly is happening here?

So what we are doing is checking to see if our current client/user has a Chat Room "identity" yet. In code prior to this conditional you will see this:



let userIDVal = localStorage.getItem('userID')
        let usernameVal = localStorage.getItem('username')


Enter fullscreen mode Exit fullscreen mode

What this code is doing, is attempting to retrieve userID and username data stored in our browsers local storage. If the data does not exist (if(!userIDVal)), we are assuming that this is the users first time in the chat room.

The first thing we do is implement a socket.on method with name "SetUserData" that will begin listening for the server to send information about this users newly made identity. Within that socket.on method_ we have some code that retrieves newly created user data from the server and then promptly saves it to the browser's local storage for future use and sets the values in the React components state. Once the user info is set, we use the socket.emit method "UserEnteredRoom" to let the server know that the user is now entering the Chat room with an identity and can chat.

After we setup the socket.on method, "SetUserData", we then implement a socket.emit method called "CreateUserData" which will be used to actually ask the server to make an identity for our user. The emit will send the message to create user data for our client and the server will promptly send a message back to be retrieved by our listening "SetUserData" method.

NOTE: The reason we don't write these two methods in reverse order is to ensure that the listener, "SetUserData", is prepared to receive newly created user data from the server before we ask the server to make new user data. In some cases, the request for new user data and its response may be fast, and if we set the listener method after the messaging function, there is a chance the listener method may still be getting setup and miss the server's response. If that doesn't make sense, please feel free to leave a comment asking for more clarification.

Now that we have added code for a first time chat user, it is also necessary for us to have things set up for a returning user with pre-existing user data stored in the browser.

In the else condition of our if(!userIDVal) conditional, we will add this code:



else {
   //If user already has userid and username, notify server to allow them to join chat
   this.setState({currentUsername: usernameVal, currentUserID: userIDVal})
   socket.emit("UserEnteredRoom", {userID: userIDVal, username: usernameVal})
}


Enter fullscreen mode Exit fullscreen mode

If the user exists in browser local storage, we simply store that information to React state and send a message to the server using the "UserEnteredRoom" socket.io method to inform our server that our client is ready to chat.

The next piece of code we will be entering will be at the end of our componentDidMountMethod(). This code will simply setup up a socket.on method called "RetrieveChatRoomData" that will always be listening for new Chat Room data, aka when a new chat message has been received by the server. This the code that we will add:



socket.on("RetrieveChatRoomData", (chatRoomData) => {
   this.setState({chatRoomData: chatRoomData}, () => this.shouldScrollToBottom())
})



Enter fullscreen mode Exit fullscreen mode

With that new addition our complete componentDidMount() method should look like this:



componentDidMount(){

    // localStorage.removeItem('userID')
    // localStorage.removeItem('username')

    let userIDVal = localStorage.getItem('userID')
    let usernameVal = localStorage.getItem('username')

    //If user does not have a userid and username saved in local storage, create them for them
    if(!userIDVal){

      socket.on("SetUserData", userData => {
        //When user creation on server is complete, retrieve and save data to local storage
        localStorage.setItem('userID', userData.userID)
        localStorage.setItem('username', userData.username)
        console.log(userData)

        this.setState({currentUsername: userData.username, currentUserID: userData.userID})

        //Notify Socket server is not ready to chat
        socket.emit("UserEnteredRoom", userData)
      });

      //Send Socket command to create user info for current user
      socket.emit("CreateUserData")
    } 
    else {
        //If user already has userid and username, notify server to allow them to join chat
        this.setState({currentUsername: usernameVal, currentUserID: userIDVal})
        socket.emit("UserEnteredRoom", {userID: userIDVal, username: usernameVal})
    }

    //Retrieve game data (from Get Chat data socket call)
    socket.on("RetrieveChatRoomData", (chatRoomData) => {
        this.setState({chatRoomData: chatRoomData}, () => this.shouldScrollToBottom())
    })

}


Enter fullscreen mode Exit fullscreen mode

Next we will implement the method that will actually send the chat messages to our server.

Find the method called sendMessageData() in our file. In this empty method we will be adding this code:



var {message, currentUsername, currentUserID} = this.state

if(message.length > 0){
    //Send chat message to server...
    socket.emit("SendMessage", {message: message, username: currentUsername, userID: currentUserID, timeStamp: null})
    //Clear chat message textfield box
    this.setState({message: ''})
}


Enter fullscreen mode Exit fullscreen mode

This code first retrieves our current entered message, our username and our userID from our React state and stores them as variables for future use. Next, we check to make sure the message has some length, otherwise we risk sending an empty message to the server. If the message length is greater than zero, we then use the socket.emit method "SendMessage" to send our typed in chat message with the information on who sent the message. NOTE: I added an extra timestamp data point just in case I wanted to add timestamps in the future to the chat view.

Once we have sent the message to the server we empty our message string from our React state which in turns clears our textfield input box.

With that last addition our Chat Room is almost ready. There is one minor housekeeping-code addition we need to add in order to prevent against possible memory leaks.

As I mentioned earlier, whenever we implement a socket.on method, we are telling our code to constantly listen to messages that may come from our server. The thing is, if you do not tell the listeners to stop listening, they will continue to listen pretty much indefinitely as long as the application is running. So if we by-chance, navigated to another page within our application, the listeners would keep on listening even though we are no longer on the page that needed the listeners. That, my friends, is essentially a memory leak. Because if we kept navigating to and from our page with the listeners it would keep adding listeners over and over and over, bogging down our application.

With all that being said, we need we need to add a set of clean-up methods that turn off those listeners when we leave the chat room page.

Somewhere in the code, preferably directly under the componentDidMount() method add this method and code:



componentWillUnmount(){
    socket.off("RetrieveChatRoomData")
    socket.off("SetUserData")
}


Enter fullscreen mode Exit fullscreen mode

As the socket.off method implies, it "turns off" the listening of these functions when we unmount this particular view or leave this page entirely.

Before we declare victory on our Chat Room app, I would like us to add one more convenience method to our React application that will allow us to clear our chat history whenever we would like.

In the "pages" folder within our "src" directory of our project, open the file titled "ClearChat.js".

At the top of the file, import socket below the last import:



...
import { Row, Container } from 'react-bootstrap';

import {socket} from '../services/socket'


Enter fullscreen mode Exit fullscreen mode

Once we have added the import, scroll down until you find the empty method called clearChatData(). In that method we will add one simple line:



socket.emit("ClearChat")


Enter fullscreen mode Exit fullscreen mode

As the socket.emit suggests, this will send a message to our Node server to clear our chat history. This functionality can only be used by navigating to the "/clearChat" page when we run our application.

With that our Chat Room application is ready to go. Before we deploy our application to the cloud, we are going to briefly test if our application is configured correctly on our local machine. This way we won't be surprised by any errors when we attempt to push it to the cloud.

Local Test of React/Node Server Connection

So let's do a quick test to see if our React application is properly configured and setup to connect with our socket.io NodeJS Server.

In a terminal/command line window, make sure you are currently in our simple-react-chat-app directory. Once we are sure we are in our directory, let's run a command that will run our React application locally. Type this command into your terminal:



npm start


Enter fullscreen mode Exit fullscreen mode

This should begin locally running our React code in our browser on our localhost (most likely port 3000). In most cases, the application should open automatically in your default browser, if not, enter http://localhost:3000 into your desired browser to view the application. If your application ran successfully, you should see something like this:

React-App-Local-Host-Chat

Alt Text

It should simply note that someone entered the chat (which was you!). At the top of the screen below the "Chat Room" title it should show who you are now appearing as in the chat. Go ahead and navigate to the bottom of the page and type something into the chat message input box. Send your message by either pressing the send icon or pressing Enter/Return. You should now see your message appear in the chat.

React-App-Hello-Chat

Alt Text

If that worked, that means your Node server in the Cloud is working and your react application is communicating correctly with it. If that did not work, go back and ensure your code matches what was shared above. NOTE: Also, check your browser web console (normally accessible via developer tools) to check if you are receiving an errors. This may give you guidance on your issue.

If you want to check what it would be like to have more than one individual in the Chat Room, Open a different browser (E.g. If you are currently testing with Firefox, now open the Chat Room with Chrome) to see how it works.

Now that we see the general chat Room functionality is working, let's just test that our Clear chat functionality is working.

In your browser address bar go to this address: http://localhost:3000/clearChat (Note: If your React is hosting at a different port replace 3000 with that port).

You should be presented with a simple page that looks like this:

React-App-Clear-Chat-View

Alt Text

Click the button on the page that says "Clear Chat".

Once you have done that, navigate back to main page (http://localhost:3000) and you should now see that the chat cleared itself. NOTE: You will only be able to clear the chat while running the application on your local machine. When hosted in cloud, I did not make it possible to route directly to Clear Chat. Feel free to add that capability if you'd like.

Now that we have tested this locally, it's time to deploy this Chat Room into the cloud. Go ahead and stop the React application so we can move on to the next steps.

Deploying your React chat Room to the Cloud

The following steps are going to be almost identical to what we did to deploy our Node server to the cloud so quite a bit of this will be familiar. The biggest difference will be the name of our Application and the route and making sure we initiate a React build before we deploy.

The first thing we are going to need to do is build our React application so it can be properly deployed on our Cloud server (IBM Cloud Cloud Foundry server). So let's go ahead and run this command to build our React app:



npm run build


Enter fullscreen mode Exit fullscreen mode

That should initiate the React build process. This may take a few minutes and your terminal window should look something like this:

CLI-successful-react-app-build

Alt Text

Now in order to have a free way to easily deploy our application to the cloud, we will be using an IBM Cloud Lite account.

If you do not have IBM Cloud Lite account, you can quickly sign-up for free access to cloud resources for hosting your application in the cloud. Signup using this link: IBM Cloud Signup Link.

Once you are signed up we will be pushing our application to the cloud using IBM Cloud CLI in our terminal. If you do not have the IBM Cloud CLI, you can download the latest version here: IBM Cloud CLI.

NOTE: If you are interested on how to do this through the IBM Cloud dashboard, you can check out my blog on deploying a React application to the cloud for general walkthrough of how that works: Deploying ReactJS to the Cloud.

Now, make sure you are still in a terminal window and that you are still in the _simple-react-chat-app directory.

While in the directory, log into your IBM Cloud account using this command:



ibmcloud login


Enter fullscreen mode Exit fullscreen mode

Enter you username and password and you should be presented with a screen like this:

CLI-IBM-Cloud-Login

CLI-Authenticated-Success

Alt Text

Once you have logged into IBM Cloud, we need to set our IBM cloud target to Cloud Foundry as we will be using the Cloud Foundry framework to host our application. If you'd like to learn more about Cloud Foundry, check out this link: Cloud Foundry. Type this command to target Cloud Foundry:



ibmcloud target --cf


Enter fullscreen mode Exit fullscreen mode

If it was successful, you should see something like this:

CLI-Target-CF

Alt Text

Right before we deploy our server to the cloud, we need to make some minor updates to our code.

In our simple-react-chat-app directory and open the file named "manifest.yml"

In the file you should see this:



applications:
  - name: Simple React Chat App
memory: 64M
path: build/
buildpack: staticfile_buildpack 
routes:
  - route: simple-react-chat-app-<your-intials>.<server-closest-to-you>.cf.appdomain.cloud


Enter fullscreen mode Exit fullscreen mode

Replace with your initials and _ with a the server closest to you. Because you may not know what are possible server names, here is a list. Choose a server that you believe you think may be closest to you. And if you want to view this list yourself you can enter the command listed below:



ibmcloud regions

//Output
Name       Display name   
au-syd     Sydney   
in-che     Chennai   
jp-osa     Osaka   
jp-tok     Tokyo   
kr-seo     Seoul   
eu-de      Frankfurt   
eu-gb      London   
ca-tor     Toronto   
us-south   Dallas   
us-east    Washington DC   
br-sao     Sao Paulo  


Enter fullscreen mode Exit fullscreen mode

For example, this was my updated manifest.yml(NOTE: Please do not use the same route name as displayed below. This will likely cause an error saying route is already taken when you attempt to push to IBM Cloud):



applications:
  - name: Simple React Chat App
memory: 64M
path: build/
buildpack: staticfile_buildpack 
routes:
  - route: simple-react-chat-app-bsh.us-south.cf.appdomain.cloud


Enter fullscreen mode Exit fullscreen mode

Once you have updated your manifest.yml, you only have one more step: Deploy your React application into the Cloud!

In the same terminal window you logged into IBM Cloud with, enter this command:



ibmcloud cf push


Enter fullscreen mode Exit fullscreen mode

This will initiate the process of deploying your NodeJS application into the cloud. It in general takes a few minutes to deploy this code.

When the the application has been successfully deployed, you should see something like this in your terminal window:

CLI-React-app-successful-push

Alt Text

Go to a browser window and insert the route value you entered into the manifest.yml. In my case that would be



simple-react-chat-app-bsh.us-south.cf.appdomain.cloud


Enter fullscreen mode Exit fullscreen mode

You should be presented with our Chat Room and with the message that a new user entered the chat. If you did not clear the chat in our previous step, you will likely see the messages you sent earlier while testing:

React-App-Deployed-Chat

Alt Text

If you do not see this page, you may need to check that you code matches the code I shared above and feel free to check the actual application status and error log by visiting the IBM Cloud Resource list and finding your application by its name.

And just like that, we have a working chat room that can be shared with whomever you like!!

I really encourage you to take the time to really understand what all this code I have shared with you is doing. Change method names, change how data is sent, and just break things! This is the best way to truly understand what is happening under the hood so that you can use socket.io to implement realtime communication in applications like this.

Since socket.io's real-time messaging can be used in a lot of different scenarios, I'd like to hear from all of you.

What other ways could you imagine using the socket.io technology other than a chat room? Leave you ideas in the the comments below. I'd love to hear them!

Thanks for reading this blog and I am encouraged by your desire to learn new topics like these.

Have a great one until next time!

==== FOLLOW ME ON SOCIAL MEDIA ====
Twitter: Bradston Dev
Dev.to: @bradstondev
Youtube: Bradston YT

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .