NodeChat. Part 2.

In my previous post I wrote about fixing some issues in NodeChat project.

All popular messengers with group chats always show us when somebody joins the group or leaves the group. I thought that it would be a nice thing to show to users, because at the time NodeChat did not show any information to people in the chat group.

To implement this I needed to do next changes:

  1. Make changes to server to broadcast the message when a user disconnects.
  2. Chat page to receive messages about user that connect and disconnect
  3. Update chat page to display the message

Server side

NodeChat is built with Socket.io. The server side of the project in the directory nodeChatServer and the code was located in index.js file.

All the events related to connect/disconnect are captured inside

 io.on(‘connection’) { … }

There are basically only two listeners I needed: “socket.on(‘disconnect’)” and “socket.on(‘user join’)”. 
In ‘user join’ I added a check to make sure that ‘username’ is not undefined, to avoid messages like “User undefined has joined the chat”.

socket.on('user join', function(username){
socket.username = username;
   if (socket.username) {
socket.broadcast.emit('user join', username);
}
})

In ‘disconnect’ I added a statement to broadcast ‘user disconnected’ message, so I could listen to it on the client side.

socket.on('disconnect', function(){
socket.broadcast.emit('user disconnected', { username: socket.username });
});

Client side

On the client side I needed to add event listeners so they would trigger appropriate functions on ‘user disconnect’ and ‘user join’:

this.state.socket.on('user join', (username) => {
this.addSystemMessage("User " + username + " joined the room." );
})

this.state.socket.on('user disconnected', (username) => { 
this.addSystemMessage("User " + username.username + " has left the room");
})

All chat messages are displayed as list elements <li> inside an unordered list <ul>. The content of messages is stored in ‘chatLog’ array.

The Render function loops through the entire array and displays the content of the ‘chatLog’.

The problem here was that when I added log messages they were displayed outside of the screen, since ‘chatPage’ covers the whole page.

So I decided to change the structure of render function. Instead of building DOM elements inside of it, I made it to use already created structures. That way, I can display any kind of content using different styles (For example bubble messages with user pictures, or centered log messages).

Here’s what it looked like:

{this.state.chatLog.map((message, index) => {
return (<li key={index} ref={el => { this.el = el; }}
className={"message " + message.messageClass}>
<div className={message.messageSenderClass}>
<img className="userNamePic" src={userImg}></img>
<p className="userName">{message.UserName}</p>
</div>
<div> {message.messageBody} <span className="messageTime">
{message.messageTime} </span>
</div>

And here is the changed version:

<ul id="chat_log">
  {this.state.greeting /*To Do make only show at first login*/}
{this.state.chatLog.map((entry, index) => {
    return (
<li key={index} ref={el => {this.el = el;}} className={"message " + entry.class}>
{entry.element}
</li>);
})}
</ul>

Now since Renderer needed DOM elements I added 2 methods:

  • One to create a structure for messages (moved logic from renderer to a separate method);
  • and the second method to create structure for system log messages:
addSystemMessage(message) {
var date = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
   var element = <div> {message} ({date}) </div>;
   var container = {
class: "systemLog",
element: element
}
   this.setState({ chatLog: [...this.state.chatLog, container] } ()=>{
this.scrollToBottom();
});
}

addSystemMessage’ will create a DOM element, including message and time, and will trigger Renderer by changing the state of the page.

I also added some css styling to format system messages to look nicer.

Now users can see system messages, when other users connect or disconnect.