This is the third part of the Building Real time private chatting app using React. In this part, you will implement below-listed features in your application,
- You will connect your React application to Socket Server.
- You will implement the Realtime chat list for the application.
- You will write Socket Server Event to Emit the updated Chat List.
- Highlight the select user from the list of users
Before Going any further a little recap,
In the previous part of the series, we completed the Login/Registration process in our application. After Login/Registration, the application will redirect the user to a home page of the application. Once you redirected to the homepage, the application will check the session of the user by making an HTTP request.
As soon as the application finishes the session checking, we will connect this React to the Nodejs socket server. Once the socket server is connected, just after that we will subscribe to real-time chat list update. So let’s get our hands dirty and start integrating real-time goodness into our application.
If you are familiar with Angular, then you will like to readhow to create Real-time private chatting app using Angular.
Connecting React application to Socket Server
We won’t just connect the Socket server by using below code, I know in most of the article you have seen, read and wrote this code to connect the Socket Server.
const socket = io('localhost:3000');
Here we will add a little extra sugar by passing a parameter while connecting to the socket server.
=>Which we will do by using the code shown below,
this.socket = io(this.BASE_URL, { query: `userId=${userId}` });
Now you may ask Why we need to this in the first place?
Well, the answer is simple. We need userId to identify the which user is just connected to the socket Server. Also, we will update the socket id of the connected user in MongoDB.
=> The updated socketId will be used at the different stages of the application.
=>Since you have everything ready to go, all you have to do is call establishSocketConnection()
(This method is defined inside the chatSocketServer.js file) from Home
Component class.
Open the home.js file and replate the componentDidMount
method by below code inHome
component class,
home.ts:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ async componentDidMount() { try { this.setRenderLoadingState(true); this.userId = await ChatHttpServer.getUserId(); const response = await ChatHttpServer.userSessionCheck(this.userId); if (response.error) { this.props.history.push(`/`) } else { this.setState({ username: response.username }); ChatHttpServer.setLS('username', response.username); ChatSocketServer.establishSocketConnection(this.userId); } this.setRenderLoadingState(false); } catch (error) { this.setRenderLoadingState(false); this.props.history.push(`/`) } }
As I said earlier, we will update the socketId of the user in the MongoDB database, hence we can use it later in the application. If you check the socket connection string in the network tab, you should see something similar to the below-shown image.
As of now, I am using the Chrome browser. Though it has nothing to do with what kind of browser you use. Anyways make sure you are passing userid while connecting socket server.
Passing user id in socket connection
Now you have provided a userId as a parameter in socket connection. Let’s complete abstract work in server side.
Open the socket.js file and add the below code to it,
socket.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */socketConfig(){ this.io.use( async (socket, next) => { try { await queryHandler.addSocketId({ userId: socket.request._query['userId'], socketId: socket.id }); next(); } catch (error) { // Error console.error(error); } }); this.socketEvents(); }
Explanation:
- The first thing to notice here we are reading the userId parameter by using line shown below,
socket.request._query['userId']
- Second, we have
addSocketId()
method to update the data in MongoDB. - And at the end, we have called rest of the socket server Event by calling
this.socketEvents()
method.
Implementing a Realtime chat List
I have divided this section into the smaller sections, since covering everything into a single section would be very nosogenic to read and understand. Creating chat list involves below-listed points,
- Creating
ChatList
component. - Writing Markup for a chat list
- Writing
ChatList
componentClass
. - Writing Socket server Event.
- Updating user’s online status in
ChatList
- Selecting a User from the chat list of the user.
By the end of this article, you will have full full-fledged working chat list for your application. So let’s dig our teeth into it and finish it.
Creating ChatList component
Why a new component for ChatList? Though we can write all the markup and Code inside the Home Component. But, It’s not a good practice and your Home Component class will be heavy and long to manage.
=> Therefore first things first, you have to createChatList
component nothing so special about it.
=>Let’s import theChatList
component intoHome
Component. Open the home.js and add the below line, so that you can show chatlist in your application,
home.js:
/* Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ import ChatList from './chat-list/ChatList';
Now you add the<ChatList>
into your home.js file, and So at this point, your home.jsfile should look like this.
home.js:
/* Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ import React, { Component } from 'react'; import { withRouter } from "react-router-dom"; import ChatSocketServer from '../../utils/ChatSocketServer'; import ChatHttpServer from '../../utils/ChatHttpServer'; import ChatList from './chat-list/ChatList'; import './Home.css'; class Home extends Component { userId = null; state = { isOverlayVisible: true, username: '______', } setRenderLoadingState = (loadingState) => { this.setState({ isOverlayVisible: loadingState }); } async componentDidMount() { try { this.setRenderLoadingState(true); this.userId = await ChatHttpServer.getUserId(); const response = await ChatHttpServer.userSessionCheck(this.userId); if (response.error) { this.props.history.push(`/`) } else { this.setState({ username: response.username }); ChatHttpServer.setLS('username', response.username); ChatSocketServer.establishSocketConnection(this.userId); } this.setRenderLoadingState(false); } catch (error) { this.setRenderLoadingState(false); this.props.history.push(`/`) } } getChatListComponent() { return this.state.isOverlayVisible ? null : <ChatList userId={this.userId} updateSelectedUser={this.updateSelectedUser}/> } render() { return ( <div className="App"> <div className = {`${this.state.isOverlayVisible ? 'overlay': 'visibility-hidden' } `}> <h1>Loading</h1> </div> <header className="app-header"> <nav className="navbar navbar-expand-md"> <h4>Hello {this.state.username} </h4> </nav> <ul className="nav justify-content-end"> <li className="nav-item"> <a className="nav-link" href="#" >Logout</a> </li> </ul> </header> <main role="main" className="container content" > <div className="row chat-content"> <div className="col-3 chat-list-container"> {this.getChatListComponent()} </div> <div className="col-8 message-container"> </div> </div> </main> </div> ); } } export default withRouter(Home)
Writing Markup for a chat list
Open thechat-list.jsand add the below-shown render method. The below code is ridiculously easy to understand.
=> The next thing ischatListUsers
property of ChatList Component class.
=> InchatListUsers
property we will store the list of the users. This variable is nothing but an array, which updates itself on certain stipulated conditions.
ChatList.js:
/* Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ render() { return ( <> <ul className={`user-list ${this.state.chatListUsers.length === 0 ? 'visibility-hidden' : ''}`} > { this.state.chatListUsers.map( (user, index) => <li key={index} className={this.state.selectedUserId === user.id ? 'active' : ''} > {user.username} <span className={user.online === 'Y' ? 'online' : 'offline'}></span> </li> ) } </ul> <div className={`alert ${this.state.loading ? 'alert-info' : ''} ${this.state.chatListUsers.length > 0 ? 'visibility-hidden' : ''}` }> { this.state.loading|| this.state.chatListUsers.length.length === 0 ? 'Loading your chat list.' : 'No User Available to chat.'} </div> </> ); }
Writing ChatList component Class
At this point, your ChatList component will render nothing. So to make it work we will add some code to the ChatList component class. Basically, in this class, do two important things.
=>First we will update the chat list and the second, we will pass the data of the selected user from the list of users to the Conversation component.
=>But first, let’s add the necessary state properties required in ChatList component class, open the ChatList.js and add the below properties,
ChatList.js:
/* Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ constructor(props) { super(props); this.state = { loading: true, selectedUserId: null, chatListUsers: [] } }
Now let’s add the code to update the chat list, this code will basically manipulate thechatListUsers
property. So open the ChatList.js and add the below code.
ChatList.js:
/* Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ componentDidMount() { const userId = this.props.userId; ChatSocketServer.getChatList(userId); ChatSocketServer.eventEmitter.on('chat-list-response', this.createChatListUsers); } componentWillUnmount() { ChatSocketServer.eventEmitter.removeListener('chat-list-response', this.createChatListUsers); } createChatListUsers = (chatListResponse) => { if (!chatListResponse.error) { let chatListUsers = this.state.chatListUsers; if (chatListResponse.singleUser) { if (chatListUsers.length > 0) { chatListUsers = chatListUsers.filter(function (obj) { return obj.id !== chatListResponse.chatList[0].id; }); } /* Adding new online user into chat list array */ chatListUsers = [...chatListUsers, ...chatListResponse.chatList]; } else if (chatListResponse.userDisconnected) { const loggedOutUser = chatListUsers.findIndex((obj) => obj.id === chatListResponse.userid); if (loggedOutUser >= 0) { chatListUsers[loggedOutUser].online = 'N'; } } else { /* Updating entire chat list if user logs in. */ chatListUsers = chatListResponse.chatList; } this.setState({ chatListUsers: chatListUsers }); } else { alert(`Unable to load Chat list, Redirecting to Login.`); } this.setState({ loading: false }); } render() { // you have already wrote the markup in the above section }
We know what the above code does, let’s understand how the above code works!
Explanation :
- Using
getChatList()
method we are updating the list of the user, which is defined into theChatSocketService
service. - For example, If a new user logs in into the system or an existing user goes offline in both the cases we will update the list of online users.
- Here I am emitting the complete list of online/offline users from the nodejs socket server to the user who justlogged into the system.
- And, for the rest of users, those who are already online will get the users information whowent offlineorjust logged in. This makes sense, right? Because sending the complete list of online users to each and every user is not good.
- Now if you see the above code there is
else if()
code block,for the below task. - If the response from the socket event contains thesingleUser propertywhich means the new socket connection is established.
- If the response contains theuserDisconnected propertythen it means the user went and offline remove the user from the chat list.
- And at the end, In the else condition we will update the list of users in the else block.
Writing Socket Server Events to Emit the updated Chat List
All the above only happen if you have Socket server and which responds to the updated chat list. Anyways, let’s go ahead and talk something constructive.
=> Here you will write socket.io.on
listener on the server side when a client requests it.
=> Once React asks for chat list to Socker server, the socket will query the details from the database.
=>After querying, based on the user it emits the result.
So this was the basic idea, let’s see the code to implement the same. Open the socket.jsof your nodejs project and add the below code,
socket.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */socket.on(`chat-list`, async (data) => { if (data.userId == '') { this.io.emit(`chat-list-response`, { error : true, message : CONSTANTS.USER_NOT_FOUND }); }else{ try { const [UserInfoResponse, chatlistResponse] = await Promise.all([ queryHandler.getUserInfo( { userId: data.userId, socketId: false }), queryHandler.getChatList( socket.id ) ]); this.io.to(socket.id).emit(`chat-list-response`, { error : false, singleUser : false, chatList : chatlistResponse }); socket.broadcast.emit(`chat-list-response`,{ error : false, singleUser : true, chatList : UserInfoResponse }); } catch ( error ) { this.io.to(socket.id).emit(`chat-list-response`,{ error : true , chatList : [] }); } } });
Explanation:
The above code seems easy to understand. But me being me, am gonna explain it anyway.
- First thing first, we will check for the validation. If all goes well, process ahead else emit a response with an error message.
- Second, inside the try-catch block, we will execute all the things. Why ? because you never know when your code will explode.
- Just kidding,try-catch is there to handle the unknown exceptions and unhandled promise rejections. And I feel like it’s a good practice to follow.
- Then inside the try-catch block, you will first fetch the information about the user by calling
getUserInfo()
. Later you all thegetChatList()
which is defined inside theQueryHandler
class. - Why are you emitting two socket Events?As I explained in the above section, you don’t wanna send the entire chat list to the user instead send only information about the user who goes online/offline.
Let’s take a look at those method defined in theQueryHandler
class. Open the query-handler.js and add the below code,
In the below code, We are just executing a MongoDB query. Based on the socketId parameter, we are projecting the result from the database.
query-handler.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */getUserInfo({userId,socketId = false}){ let queryProjection = null; if(socketId){ queryProjection = { "socketId" : true } } else { queryProjection = { "username" : true, "online" : true, '_id': false, 'id': '$_id' } } return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').aggregate([{ $match: { _id : ObjectID(userId) } },{ $project : queryProjection } ]).toArray( (err, result) => { DB.close(); if( err ){ reject(err); } socketId ? resolve(result[0]['socketId']) : resolve(result); }); } catch (error) { reject(error) } }); }
Now the last method left is to fetch the chat list from the database. Honestly Speaking you are just fetching all the user from the database and displaying it to the front end based on the online/offline status.
Open the query-handler.js and add the below code. In the below code, we will fetch all the records from the database.
query-handler.js:
/* * Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ getChatList(userId){ return new Promise( async (resolve, reject) => { try { const [DB,ObjectID] = await this.Mongodb.onConnect(); DB.collection('users').aggregate([{ $match: { 'socketId': { $ne : userId} } },{ $project:{ "username" : true, "online" : true, '_id': false, 'id': '$_id' } } ]).toArray( (err, result) => { DB.close(); if( err ){ reject(err); } resolve(result); }); } catch (error) { reject(error) } }); }
In the above code, we are executing the MongoDB query. This query will fetch the list of users from the database by using the given condition. After executing the query, it will return the result trough Promise.
Once you implement all this you should be properly working chat list as shown in below image,
Realtime Chat list
Selecting a user from the chat list to chat
Till now you have successfully implemented the chat list feature in your application. Let’s go one more step ahead and add the feature to highlight the user selected for the chatting.
=>In this section we will just highlight the selected user from the chat list.
=> Open the ChatList.jsand replace the code, Here we are using bootstrap classes, so you don’t need to write any CSS for that.
ChatList.js:
/* Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ render() { return ( <> <ul className={`user-list ${this.state.chatListUsers.length === 0 ? 'visibility-hidden' : ''}`} > { this.state.chatListUsers.map( (user, index) => <li key={index} className={this.state.selectedUserId === user.id ? 'active' : ''} onClick={() => this.selectedUser(user)} > {user.username} <span className={user.online === 'Y' ? 'online' : 'offline'}></span> </li> ) } </ul> <div className={`alert ${this.state.loading ? 'alert-info' : ''} ${this.state.chatListUsers.length > 0 ? 'visibility-hidden' : ''}` }> { this.state.loading|| this.state.chatListUsers.length.length === 0 ? 'Loading your chat list.' : 'No User Available to chat.'} </div> </> ); }
Explanation:
- As you can see the method
this.state.selectedUserId
will return true or false, and with the help of that, we will show currently selected user. - By comparing the user selected userId with the user id.
=>In ChatListComponent class, we have selectedUserId state property as shown below.
selectedUserId: null,
This property will hold the value of the selected user id. To complete this feature, add the below methods in the ChatList.js.
ChatList.js:
/* Real time private chatting app using React, Nodejs, mongodb and Socket.io * @author Shashank Tiwari */ selectedUser = (user) => { this.setState({ selectedUserId: user.id }); this.props.updateSelectedUser(user) }
Explanation:
selectedUser()
method will update theselectedUserId
state property. Also, this method will emit the latest selected user id using the behavior subject.- Also, this method will call a props method, i.e. updateSelectedUser which will give the currently selected user to its parent component.
Finishing Note
I believe this part of the series was short as compare to others. Let’s recall one more time, what we implemented here before wrapping this part. We implemented below-listed features in our application,
- We connected our React application to Socket Server.
- We implemented the Realtime chat list for the application.
- We wrote Socket Server Event to Emit the updated Chat List.
- Highlighted the select user from the list of users
For now, that’s it. I’ll see you in next and the last of the series where will implement real-time chat feature.
Hi, may I know when will the last part will be posted?
Hey I just did, https://www.codershood.info/2019/10/31/real-time-private-chatting-app-using-react-nodejs-mongodb-and-socket-io-part-4/
Hi I left a comment on part 4 also, your source code doesn’t seem to work, have you tested it?
I get errors about toLowerCase() of undefined
if I remove it and try to call api using postman I get server error, can’t create user..
Hey Can you post the error? And just to check you have downloaded the code from Github?
Hi, yes I cloned the git repo, below is a list of instructions that I complete:
1) Run ‘npm install’ for both the React application and the server
2) For both folders (react app and server) I have to update some dependencies as they are outdated and no longer available. For example bcrypt is now bcryptjs I believe.
3) I run ‘npm start’ for react application and have to fix some filenames as you have specified the file name having a capital letter at the beginning when it doesn’t have one so that throw multiple errors.
4) After fixing the filenames I try and run ‘npm start’ again, this time I get an error which reads:
*Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it’s defined in, or you might have mixed up default and named imports.
Check the render method of `Login`* <— I get the same for Registration