category: How toAug 15, 2022

Building a chat app with Socket.io and React 🚀

We have all encountered chat over the web, that can be Facebook, Instagram, Whatsapp and the list goes on. Just to give a bit of context, you send a message to a person or a group, they see the message and reply back. Simple yet complex. To develop a chat app you would need to be aware of new messages as soon as they arrive. Usually, to get information from the server you need send an HTTP request. With websockets, the server lets you know when there is new information without asking it.

In this article, we’ll leverage the real-time communication provided by Socket.io to create an open chat application that allows users to send and receive messages from several users on the application. You will also learn how to detect the users who are online and when a user is typing.

💡 To read this article you’ll need to have a basic knowledge of React.js and Node.js to comprehend this article.

Chat

What is Socket.io?

Socket.io is a popular JavaScript library that allows us to create real-time, bi-directional communication between web browsers and a Node.js server. It is a highly performant and reliable library optimized to process a large volume of data with minimal delay. It follows the WebSocket protocol and provides better functionalities, such as fallback to HTTP long-polling or automatic reconnection, which enables us to build efficient chat and real-time applications.

Novu – the first open-source notification architecture

Just a quick background about us. Novu is the first open-source notification infrastructure. We basically help to manage all the product notifications. It can be In-App (the bell icon like you have in Facebook – Websockets), Emails, SMSs, and so on.
We are trending now on Github, and every additional star can help us to reach more developers. Happy if you can help me out ❤️
https://github.com/novuhq/novu

How to connect a React.js app to Node.js via Socket.io

In this section, we’ll set up the project environment for our chat application. You’ll also learn how to add Socket.io to a React and Node.js application and connect both development servers for real-time communication via Socket.io.

Create the project folder containing two sub-folders named client and server.

1mkdir chat-app
2cd chat-app
3mkdir client server
4

Navigate into the client folder via your terminal and create a new React.js project.

1cd client
2npx create-react-app ./

Install Socket.io client API and React Router. React Router is a JavaScript library that enables us to navigate between pages in a React application.

1npm install socket.io-client react-router-dom

Delete the redundant files such as the logo and the test files from the React app, and update the App.js file to display Hello World as below.

1function App() {
2  return (
3    <div>
4      <p>Hello World!</p>
5    </div>
6  );
7}

Next, navigate into the server folder and create a package.json file.

1cd server
2npm init -y

Install Express.js, CORS, Nodemon, and Socket.io Server API.

Express.js is a fast, minimalist framework that provides several features for building web applications in Node.js. CORS is a Node.js package that allows communication between different domains.

Nodemon is a Node.js tool that automatically restarts the server after detecting file changes, and Socket.io allows us to configure a real-time connection on the server.

1npm install express cors nodemon socket.io 

Create an index.js file – the entry point to the web server.

1touch index.js

Set up a simple Node.js server using Express.js. The code snippet below returns a JSON object when you visit the http://localhost:4000/api in your browser.

1//index.js
2const express = require('express');
3const app = express();
4const PORT = 4000;
5
6app.get('/api', (req, res) => {
7  res.json({
8    message: 'Hello world',
9  });
10});
11
12app.listen(PORT, () => {
13  console.log(`Server listening on ${PORT}`);
14});
15

Import the HTTP and the CORS library to allow data transfer between the client and the server domains.

1const express = require('express');
2const app = express();
3const PORT = 4000;
4
5//New imports
6const http = require('http').Server(app);
7const cors = require('cors');
8
9app.use(cors());
10
11app.get('/api', (req, res) => {
12  res.json({
13    message: 'Hello world',
14  });
15});
16
17http.listen(PORT, () => {
18  console.log(`Server listening on ${PORT}`);
19});

Next, add Socket.io to the project to create a real-time connection. Before the app.get() block, copy the code below.

1//New imports
2.....
3const socketIO = require('socket.io')(http, {
4    cors: {
5        origin: "http://localhost:3000"
6    }
7});
8
9//Add this before the app.get() block
10socketIO.on('connection', (socket) => {
11    console.log(`⚡: ${socket.id} user just connected!`);
12    socket.on('disconnect', () => {
13      console.log('🔥: A user disconnected');
14    });
15});

From the code snippet above, the socket.io("connection") function establishes a connection with the React app, then creates a unique ID for each socket and logs the ID to the console whenever a user visits the web page.

When you refresh or close the web page, the socket fires the disconnect event showing that a user has disconnected from the socket.

Next, configure Nodemon by adding the start command to the list of the scripts in the package.json file. The code snippet below starts the server using Nodemon.

1//In server/package.json
2
3"scripts": {
4    "test": "echo \"Error: no test specified\" && exit 1",
5    "start": "nodemon index.js"
6  },

You can now run the server with Nodemon by using the command below.

1npm start
2

Open the App.js file in the client folder and connect the React app to the Socket.io server.

1import socketIO from 'socket.io-client';
2const socket = socketIO.connect('http://localhost:4000');
3
4function App() {
5  return (
6    <div>
7      <p>Hello World!</p>
8    </div>
9  );
10}

Start the React.js server.

1npm start

Check the terminal where the server is running; the ID of the React.js client appears in the terminal.

Congratulations 🥂 , the React app has been successfully connected to the server via Socket.io.

💡 For the remaining part of this article, I will walk you through creating the web pages for the chat application and sending messages back and forth between the React app and the Node.js server. I’ll also guide you on how to add the auto-scroll feature when a new message arrives and how to fetch active users in your chat application.

Creating the Home page for the chat application

In this section, we’ll create the home page for the chat application that accepts the username and saves it to the local storage for identification.

Create a folder named components within the client/src folder. Then, create the Home page component.

1cd src
2mkdir components & cd components
3touch Home.js

Copy the code below into the Home.js file. The code snippet displays a form input that accepts the username and stores it in the local storage.

1import React, { useState } from 'react';
2import { useNavigate } from 'react-router-dom';
3
4const Home = () => {
5  const navigate = useNavigate();
6  const [userName, setUserName] = useState('');
7
8  const handleSubmit = (e) => {
9    e.preventDefault();
10    localStorage.setItem('userName', userName);
11    navigate('/chat');
12  };
13  return (
14    <form className="home__container" onSubmit={handleSubmit}>
15      <h2 className="home__header">Sign in to Open Chat</h2>
16      <label htmlFor="username">Username</label>
17      <input
18        type="text"
19        minLength={6}
20        name="username"
21        id="username"
22        className="username__input"
23        value={userName}
24        onChange={(e) => setUserName(e.target.value)}
25      />
26      <button className="home__cta">SIGN IN</button>
27    </form>
28  );
29};
30
31export default Home;

Next, configure React Router to enable navigation between the pages of the chat application. A home and chat page is enough for this application.

Copy the code below into the src/App.js file.

1import { BrowserRouter, Routes, Route } from 'react-router-dom';
2import Home from './components/Home';
3import ChatPage from './components/ChatPage';
4import socketIO from 'socket.io-client';
5
6const socket = socketIO.connect('http://localhost:4000');
7function App() {
8  return (
9    <BrowserRouter>
10      <div>
11        <Routes>
12          <Route path="/" element={<Home socket={socket} />}></Route>
13          <Route path="/chat" element={<ChatPage socket={socket} />}></Route>
14        </Routes>
15      </div>
16    </BrowserRouter>
17  );
18}
19
20export default App;

The code snippet assigns different routes for the Home and Chat page of the application using React Router v6 and passes the Socket.io library into the components. We’ll create the Chat page in the upcoming section.

Navigate into the src/index.css file and copy the code below. It contains all the CSS required for styling this project.

1@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap');
2
3* {
4  box-sizing: border-box;
5  margin: 0;
6  padding: 0;
7  font-family: 'Poppins', sans-serif;
8}
9.home__container {
10  width: 100%;
11  height: 100vh;
12  display: flex;
13  flex-direction: column;
14  justify-content: center;
15  align-items: center;
16}
17.home__container > * {
18  margin-bottom: 10px;
19}
20.home__header {
21  margin-bottom: 30px;
22}
23.username__input {
24  padding: 10px;
25  width: 50%;
26}
27.home__cta {
28  width: 200px;
29  padding: 10px;
30  font-size: 16px;
31  cursor: pointer;
32  background-color: #607eaa;
33  color: #f9f5eb;
34  outline: none;
35  border: none;
36  border-radius: 5px;
37}
38.chat {
39  width: 100%;
40  height: 100vh;
41  display: flex;
42  align-items: center;
43}
44.chat__sidebar {
45  height: 100%;
46  background-color: #f9f5eb;
47  flex: 0.2;
48  padding: 20px;
49  border-right: 1px solid #fdfdfd;
50}
51.chat__main {
52  height: 100%;
53  flex: 0.8;
54}
55.chat__header {
56  margin: 30px 0 20px 0;
57}
58.chat__users > * {
59  margin-bottom: 10px;
60  color: #607eaa;
61  font-size: 14px;
62}
63.online__users > * {
64  margin-bottom: 10px;
65  color: rgb(238, 102, 102);
66  font-style: italic;
67}
68.chat__mainHeader {
69  width: 100%;
70  height: 10vh;
71  display: flex;
72  align-items: center;
73  justify-content: space-between;
74  padding: 20px;
75  background-color: #f9f5eb;
76}
77.leaveChat__btn {
78  padding: 10px;
79  width: 150px;
80  border: none;
81  outline: none;
82  background-color: #d1512d;
83  cursor: pointer;
84  color: #eae3d2;
85}
86.message__container {
87  width: 100%;
88  height: 80vh;
89  background-color: #fff;
90  padding: 20px;
91  overflow-y: scroll;
92}
93
94.message__container > * {
95  margin-bottom: 10px;
96}
97.chat__footer {
98  padding: 10px;
99  background-color: #f9f5eb;
100  height: 10vh;
101}
102.form {
103  width: 100%;
104  height: 100%;
105  display: flex;
106  align-items: center;
107  justify-content: space-between;
108}
109.message {
110  width: 80%;
111  height: 100%;
112  border-radius: 10px;
113  border: 1px solid #ddd;
114  outline: none;
115  padding: 15px;
116}
117.sendBtn {
118  width: 150px;
119  background-color: green;
120  padding: 10px;
121  border: none;
122  outline: none;
123  color: #eae3d2;
124  cursor: pointer;
125}
126.sendBtn:hover {
127  background-color: rgb(129, 201, 129);
128}
129.message__recipient {
130  background-color: #f5ccc2;
131  width: 300px;
132  padding: 10px;
133  border-radius: 10px;
134  font-size: 15px;
135}
136.message__sender {
137  background-color: rgb(194, 243, 194);
138  max-width: 300px;
139  padding: 10px;
140  border-radius: 10px;
141  margin-left: auto;
142  font-size: 15px;
143}
144.message__chats > p {
145  font-size: 13px;
146}
147.sender__name {
148  text-align: right;
149}
150.message__status {
151  position: fixed;
152  bottom: 50px;
153  font-size: 13px;
154  font-style: italic;
155}

We’ve created the home page of our chat application. Next, let’s design the user interface for the chat page.

Creating the Chat page of the application

In this section, we’ll create the chat interface that allows us to send messages and view active users.

chat-app interface

From the image above, the Chat page is divided into three sections, the Chat Bar – sidebar showing active users, the Chat Body containing the sent messages and the header, and the Chat Footer – the message box and the send button.

Since we’ve been able to define the layout for the chat page, you can now create the components for the design.

Create the ChatPage.js file and copy the code below into it. You will need to ChatBar, ChatBody, and ChatFooter components.

1import React from 'react';
2import ChatBar from './ChatBar';
3import ChatBody from './ChatBody';
4import ChatFooter from './ChatFooter';
5
6const ChatPage = ({ socket }) => {
7  return (
8    <div className="chat">
9      <ChatBar />
10      <div className="chat__main">
11        <ChatBody />
12        <ChatFooter />
13      </div>
14    </div>
15  );
16};
17
18export default ChatPage;

The Chat Bar component

Copy the code below into the ChatBar.js file.

1import React from 'react';
2
3const ChatBar = () => {
4  return (
5    <div className="chat__sidebar">
6      <h2>Open Chat</h2>
7
8      <div>
9        <h4 className="chat__header">ACTIVE USERS</h4>
10        <div className="chat__users">
11          <p>User 1</p>
12          <p>User 2</p>
13          <p>User 3</p>
14          <p>User 4</p>
15        </div>
16      </div>
17    </div>
18  );
19};
20
21export default ChatBar;

The Chat Body component

Here, we’ll create the interface displaying the sent messages and the page headline.

1import React from 'react';
2import { useNavigate } from 'react-router-dom';
3
4const ChatBody = () => {
5  const navigate = useNavigate();
6
7  const handleLeaveChat = () => {
8    localStorage.removeItem('userName');
9    navigate('/');
10    window.location.reload();
11  };
12
13  return (
14    <>
15      <header className="chat__mainHeader">
16        <p>Hangout with Colleagues</p>
17        <button className="leaveChat__btn" onClick={handleLeaveChat}>
18          LEAVE CHAT
19        </button>
20      </header>
21
22      {/*This shows messages sent from you*/}
23      <div className="message__container">
24        <div className="message__chats">
25          <p className="sender__name">You</p>
26          <div className="message__sender">
27            <p>Hello there</p>
28          </div>
29        </div>
30
31        {/*This shows messages received by you*/}
32        <div className="message__chats">
33          <p>Other</p>
34          <div className="message__recipient">
35            <p>Hey, I'm good, you?</p>
36          </div>
37        </div>
38
39        {/*This is triggered when a user is typing*/}
40        <div className="message__status">
41          <p>Someone is typing...</p>
42        </div>
43      </div>
44    </>
45  );
46};
47
48export default ChatBody;

Here, we’ll create the input and the send button at the bottom of the chat page. The message and the username appear in the console after submitting the form.

1import React, { useState } from 'react';
2
3const ChatFooter = () => {
4  const [message, setMessage] = useState('');
5
6  const handleSendMessage = (e) => {
7    e.preventDefault();
8    console.log({ userName: localStorage.getItem('userName'), message });
9    setMessage('');
10  };
11  return (
12    <div className="chat__footer">
13      <form className="form" onSubmit={handleSendMessage}>
14        <input
15          type="text"
16          placeholder="Write message"
17          className="message"
18          value={message}
19          onChange={(e) => setMessage(e.target.value)}
20        />
21        <button className="sendBtn">SEND</button>
22      </form>
23    </div>
24  );
25};
26
27export default ChatFooter;

Sending messages between the React app and the Socket.io server

In this section, you’ll learn how to send messages from the React app to the Node.js server and vice-versa via Socket.io. To send the messages to the server, we will need to pass the Socket.io library into the ChatFooter – component that sends the messages.

Update the ChatPage.js file to pass the Socket.io library into the ChatFooter component.

1import React from 'react';
2import ChatBar from './ChatBar';
3import ChatBody from './ChatBody';
4import ChatFooter from './ChatFooter';
5
6const ChatPage = ({ socket }) => {
7  return (
8    <div className="chat">
9      <ChatBar />
10      <div className="chat__main">
11        <ChatBody />
12        <ChatFooter socket={socket} />
13      </div>
14    </div>
15  );
16};
17
18export default ChatPage;

Update the handleSendMessage function in the ChatFooter component to send the message to the Node.js server.

1import React, { useState } from 'react';
2
3const ChatFooter = ({ socket }) => {
4  const [message, setMessage] = useState('');
5
6  const handleSendMessage = (e) => {
7    e.preventDefault();
8    if (message.trim() && localStorage.getItem('userName')) {
9      socket.emit('message', {
10        text: message,
11        name: localStorage.getItem('userName'),
12        id: `${socket.id}${Math.random()}`,
13        socketID: socket.id,
14      });
15    }
16    setMessage('');
17  };
18  return <div className="chat__footer">...</div>;
19};
20
21export default ChatFooter;

The handleSendMessage function checks if the text field is empty and if the username exists in the local storage (sign-in from the Home page) before sending the message event containing the user input, username, the message ID generated, and the socket or client ID to the Node.js server.

Open the index.js file on the server, update the Socket.io code block to listen to the message event from the React app client, and log the message to the server’s terminal.

1socketIO.on('connection', (socket) => {
2  console.log(`⚡: ${socket.id} user just connected!`);
3
4  //Listens and logs the message to the console
5  socket.on('message', (data) => {
6    console.log(data);
7  });
8
9  socket.on('disconnect', () => {
10    console.log('🔥: A user disconnected');
11  });
12});

We’ve been able to retrieve the message on the server; hence, let’s send the message to all the connected clients.

1socketIO.on('connection', (socket) => {
2  console.log(`⚡: ${socket.id} user just connected!`);
3
4  //sends the message to all the users on the server
5  socket.on('message', (data) => {
6    socketIO.emit('messageResponse', data);
7  });
8
9  socket.on('disconnect', () => {
10    console.log('🔥: A user disconnected');
11  });
12});

Update the ChatPage.js file to listen to the message from the server and display it to all users.

1import React, { useEffect, useState } from 'react';
2import ChatBar from './ChatBar';
3import ChatBody from './ChatBody';
4import ChatFooter from './ChatFooter';
5
6const ChatPage = ({ socket }) => {
7  const [messages, setMessages] = useState([]);
8
9  useEffect(() => {
10    socket.on('messageResponse', (data) => setMessages([...messages, data]));
11  }, [socket, messages]);
12
13  return (
14    <div className="chat">
15      <ChatBar socket={socket} />
16      <div className="chat__main">
17        <ChatBody messages={messages} />
18        <ChatFooter socket={socket} />
19      </div>
20    </div>
21  );
22};
23
24export default ChatPage;

From the code snippet above, Socket.io listens to the messages sent via the messageResponse event and spreads the data into the messages array. The array of messages is passed into the ChatBody component for display on the UI.

Update the ChatBody.js file to render the data from the array of messages.

1import React from 'react';
2import { useNavigate } from 'react-router-dom';
3
4const ChatBody = ({ messages }) => {
5  const navigate = useNavigate();
6
7  const handleLeaveChat = () => {
8    localStorage.removeItem('userName');
9    navigate('/');
10    window.location.reload();
11  };
12
13  return (
14    <>
15      <header className="chat__mainHeader">
16        <p>Hangout with Colleagues</p>
17        <button className="leaveChat__btn" onClick={handleLeaveChat}>
18          LEAVE CHAT
19        </button>
20      </header>
21
22      <div className="message__container">
23        {messages.map((message) =>
24          message.name === localStorage.getItem('userName') ? (
25            <div className="message__chats" key={message.id}>
26              <p className="sender__name">You</p>
27              <div className="message__sender">
28                <p>{message.text}</p>
29              </div>
30            </div>
31          ) : (
32            <div className="message__chats" key={message.id}>
33              <p>{message.name}</p>
34              <div className="message__recipient">
35                <p>{message.text}</p>
36              </div>
37            </div>
38          )
39        )}
40
41        <div className="message__status">
42          <p>Someone is typing...</p>
43        </div>
44      </div>
45    </>
46  );
47};
48
49export default ChatBody;

The code snippet above displays the messages depending on whether you or another user sent the message. Messages in green are the ones you sent, and red is messages from other users.

Congratulations 🥂, the chat application is now functional. You can open multiple tabs and send messages from one to another.

How to fetch active users from Socket.io

In this section, you’ll learn how to get all the active users and display them on the Chat Bar of the chat application.

chat-app interface

Open the src/Home.js and create an event that listens to users when they sign in. Update the handleSubmit function as below:

1import React, { useState } from 'react';
2import { useNavigate } from 'react-router-dom';
3
4const Home = ({ socket }) => {
5  const navigate = useNavigate();
6  const [userName, setUserName] = useState('');
7
8  const handleSubmit = (e) => {
9    e.preventDefault();
10    localStorage.setItem('userName', userName);
11    //sends the username and socket ID to the Node.js server
12    socket.emit('newUser', { userName, socketID: socket.id });
13    navigate('/chat');
14  };
15  return (...)
16  ...

Create an event listener that updates an array of users on the Node.js server whenever a user joins or leaves the chat application.

1let users = [];
2
3socketIO.on('connection', (socket) => {
4  console.log(`⚡: ${socket.id} user just connected!`);
5  socket.on('message', (data) => {
6    socketIO.emit('messageResponse', data);
7  });
8
9  //Listens when a new user joins the server
10  socket.on('newUser', (data) => {
11    //Adds the new user to the list of users
12    users.push(data);
13    // console.log(users);
14    //Sends the list of users to the client
15    socketIO.emit('newUserResponse', users);
16  });
17
18  socket.on('disconnect', () => {
19    console.log('🔥: A user disconnected');
20    //Updates the list of users when a user disconnects from the server
21    users = users.filter((user) => user.socketID !== socket.id);
22    // console.log(users);
23    //Sends the list of users to the client
24    socketIO.emit('newUserResponse', users);
25    socket.disconnect();
26  });
27});

socket.on("newUser") is triggered when a new user joins the chat application. The user’s details (socket ID and username) are saved into the users array and sent back to the React app in a new event named newUserResponse.
In socket.io("disconnect"), the users array is updated when a user leaves the chat application, and the newUserReponse event is triggered to send the updated the list of users to the client.

Next, let’s update the user interface, ChatBar.js, to display the list of active users.

1import React, { useState, useEffect } from 'react';
2
3const ChatBar = ({ socket }) => {
4  const [users, setUsers] = useState([]);
5
6  useEffect(() => {
7    socket.on('newUserResponse', (data) => setUsers(data));
8  }, [socket, users]);
9
10  return (
11    <div className="chat__sidebar">
12      <h2>Open Chat</h2>
13      <div>
14        <h4 className="chat__header">ACTIVE USERS</h4>
15        <div className="chat__users">
16          {users.map((user) => (
17            <p key={user.socketID}>{user.userName}</p>
18          ))}
19        </div>
20      </div>
21    </div>
22  );
23};
24
25export default ChatBar;

The useEffect hook listens to the response sent from the Node.js server and collects the list of active users. The list is mapped into the view and updated in real-time.

Congratulations 💃🏻, we’ve been able to fetch the list of active users from Socket.io. Next, let’s learn how to add some cool features to the chat application.

Optional: Auto-scroll and Notify users when a user is typing

In this section, you’ll learn how to add the auto-scroll feature when you receive a new message and the typing feature that indicates that a user is typing.

Auto-scroll feature

Auto Scroll

Update the ChatPage.js file as below:

1import React, { useEffect, useState, useRef } from 'react';
2import ChatBar from './ChatBar';
3import ChatBody from './ChatBody';
4import ChatFooter from './ChatFooter';
5
6const ChatPage = ({ socket }) => {
7  const [messages, setMessages] = useState([]);
8  const [typingStatus, setTypingStatus] = useState('');
9  const lastMessageRef = useRef(null);
10
11  useEffect(() => {
12    socket.on('messageResponse', (data) => setMessages([...messages, data]));
13  }, [socket, messages]);
14
15  useEffect(() => {
16    // 👇️ scroll to bottom every time messages change
17    lastMessageRef.current?.scrollIntoView({ behavior: 'smooth' });
18  }, [messages]);
19
20  return (
21    <div className="chat">
22      <ChatBar socket={socket} />
23      <div className="chat__main">
24        <ChatBody messages={messages} lastMessageRef={lastMessageRef} />
25        <ChatFooter socket={socket} />
26      </div>
27    </div>
28  );
29};
30
31export default ChatPage;

Update the ChatBody component to contain an element for lastMessageRef.

1import React from 'react';
2import { useNavigate } from 'react-router-dom';
3
4const ChatBody = ({ messages, lastMessageRef }) => {
5  const navigate = useNavigate();
6
7  const handleLeaveChat = () => {
8    localStorage.removeItem('userName');
9    navigate('/');
10    window.location.reload();
11  };
12
13  return (
14    <>
15      <div>
16        ......
17        {/* --- At the bottom of the JSX element ----*/}
18        <div ref={lastMessageRef} />
19      </div>
20    </>
21  );
22};
23
24export default ChatBody;

From the code snippets above, lastMessageRef is attached to a div tag at the bottom of the messages, and its useEffect has a single dependency, which is the messages array. So, when the messages changes, the useEffect for the lastMessageRef re-renders.

Notify others when a user is typing

To notify users when a user is typing, we’ll use the JavaScript onKeyDown event listener on the input field, which triggers a function that sends a message to Socket.io as below:

1import React, { useState } from 'react';
2
3const ChatFooter = ({ socket }) => {
4  const [message, setMessage] = useState('');
5
6  const handleTyping = () =>
7    socket.emit('typing', `${localStorage.getItem('userName')} is typing`);
8
9  const handleSendMessage = (e) => {
10    e.preventDefault();
11    if (message.trim() && localStorage.getItem('userName')) {
12      socket.emit('message', {
13        text: message,
14        name: localStorage.getItem('userName'),
15        id: `${socket.id}${Math.random()}`,
16        socketID: socket.id,
17      });
18    }
19    setMessage('');
20  };
21  return (
22    <div className="chat__footer">
23      <form className="form" onSubmit={handleSendMessage}>
24        <input
25          type="text"
26          placeholder="Write message"
27          className="message"
28          value={message}
29          onChange={(e) => setMessage(e.target.value)}
30                    {/*OnKeyDown function*/}
31          onKeyDown={handleTyping}
32        />
33        <button className="sendBtn">SEND</button>
34      </form>
35    </div>
36  );
37};
38
39export default ChatFooter;

From the code snippet above, the handleTyping function triggers the typing event whenever a user is typing into the text field. Then, we can listen to the typing event on the server and send a response containing the data to other users via another event called typingResponse.

1socketIO.on('connection', (socket) => {
2  // console.log(`⚡: ${socket.id} user just connected!`);
3  // socket.on('message', (data) => {
4  //   socketIO.emit('messageResponse', data);
5  // });
6
7  socket.on('typing', (data) => socket.broadcast.emit('typingResponse', data));
8
9  // socket.on('newUser', (data) => {
10  //   users.push(data);
11  //   socketIO.emit('newUserResponse', users);
12  // });
13
14  // socket.on('disconnect', () => {
15  //   console.log('🔥: A user disconnected');
16  //   users = users.filter((user) => user.socketID !== socket.id);
17  //   socketIO.emit('newUserResponse', users);
18  //   socket.disconnect();
19  // });
20});

Next, listen to the typingResponse event in the ChatPage.js file and pass the data into the ChatBody.js file for display.

1import React, { useEffect, useState, useRef } from 'react';
2import ChatBar from './ChatBar';
3import ChatBody from './ChatBody';
4import ChatFooter from './ChatFooter';
5
6const ChatPage = ({ socket }) => {
7  // const [messages, setMessages] = useState([]);
8  // const [typingStatus, setTypingStatus] = useState('');
9  // const lastMessageRef = useRef(null);
10
11  // useEffect(() => {
12  //   socket.on('messageResponse', (data) => setMessages([...messages, data]));
13  // }, [socket, messages]);
14
15  // useEffect(() => {
16  //   // 👇️ scroll to bottom every time messages change
17  //   lastMessageRef.current?.scrollIntoView({ behavior: 'smooth' });
18  // }, [messages]);
19
20  useEffect(() => {
21    socket.on('typingResponse', (data) => setTypingStatus(data));
22  }, [socket]);
23
24  return (
25    <div className="chat">
26      <ChatBar socket={socket} />
27      <div className="chat__main">
28        <ChatBody
29          messages={messages}
30          typingStatus={typingStatus}
31          lastMessageRef={lastMessageRef}
32        />
33        <ChatFooter socket={socket} />
34      </div>
35    </div>
36  );
37};
38
39export default ChatPage;

Update the ChatBody.js file to show the typing status to the users.

1<div className="message__status">
2  <p>{typingStatus}</p>
3</div>

Congratulations, you’ve just created a chat application!💃🏻

Feel free to improve the application by adding the Socket.io private messaging feature that allows users to create private chat rooms and direct messaging, using an authentication library for user authorization and authentication and a real-time database for storage.

Conclusion

Socket.io is a great tool with excellent features that enables us to build efficient real-time applications like sports betting websites, auction and forex trading applications, and of course, chat applications by creating lasting connections between web browsers and a Node.js server.

If you’re looking forward to building a chat application in Node.js, Socket.io may be an excellent choice.

You can find the source code for this tutorial here: https://github.com/novuhq/blog/tree/main/open-chat-app-with-socketIO

Next article

In the next part of the series I am going to talk about connecting the chat-app into browser notifications (web-push), so you can inform users about new messages if they are offline.

Help me out!

If you feel like this article helped you understand WebSockets better! I would be super happy if you could give us a star! And let me also know in the comments ❤️
https://github.com/novuhq/novu

Image description

Thank you for reading!

Related Posts

category: How to

How to Build a Notion-Like Notification Inbox with Chakra UI and Novu

Learn how to build a Notion-inspired real-time notification inbox in React using Chakra UI and Novu's customizable notification component. Includes code examples, styling tips, and a live demo.

Emil Pearce
Emil Pearce
category: How to

Build a Real-time Notification System with Socket.IO and ReactJS

Learn how to build a real-time notification system in a chat app with ReactJS and Socket.io. This step-by-step guide covers setup, event handling, notifications, and best practices.

Emil Pearce
Emil Pearce
category: How to

Case Study: How Novu Migrated User Management to Clerk

Discover how Novu implemented Clerk for user management, enabling features like SAML SSO, OAuth providers, and multi-factor authentication. Learn about the challenges faced and the innovative solutions developed by our team. This case study provides insights into our process, integration strategies that made it possible.

Emil Pearce
Emil Pearce