React/ReactJS: Chat w/ Node.js & WebSocket
For implementing real-time applications like chat, live video streaming, online games, stock trading, etc, the traditional browser/server request/response methodology over HTTP protocol proves to be inefficient. You need a full-duplex client-server connection system.
For such purposes, we have the WebSocket API which gives persistent bi-directional TCP connection between a client (browser) and a server in real-time.
In this tutorial, we will create a simple chat application in React and Node.js using WebSocket.
Install ws, a WebSocket library in Node.js.
npm i ws
We now create a WebSocket server in Node.js as shown below. Include ws
, the above installed WebSocket library. The port number we assign is 8080
(you can pick any other number which does not conflict with your React application port). We name this file Server.js
.
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(data) {
wss.clients.forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(data);
// console.log('data', data);
}
});
});
});
The client !== ws
condition inside the if
statement is for the WebSocket on the client side which broadcasts to every other connected WebSocket clients, but excludes itself. You can uncomment the // console.log('data', data);
statement in the above code to view the transmitted messages in real-time logged into the console.
The client side code (React) goes inside a function component.
Just after the import
statement(s), we first define const URL
and assign it the 'localhost
' ip value where the protocol is ws://
(and not the usual http://
or https://
) and the port number is 8080
, which is the very number assigned to the server.
const URL = 'ws://127.0.0.1:8080';
We next create four states using the useState hook:
-
user
, for storing the name/id of the user -
message
, for assigning the latest message sent by a user -
messages
, to store up all messages exchanged between clients -
ws
, the instantiated WebSocket object. Note that this WebSocket is the WebSocket API and not the installedws
library used inServer.js
.
const [user, setUser] = useState('John');
const [message, setMessage] = useState([]);
const [messages, setMessages] = useState([]);
const [ws, setWs] = useState(new WebSocket(URL));
Also, in our simplistic design of chat UI, a message (text) input box and a submit button alongside it is generated via JSX, on press of which a stringified message (along with the sender's name) is sent to the server and at the same time appended to the messages
state array.
const submitMessage = (usr, msg) => {
const message = { user: usr, message: msg };
ws.send(JSON.stringify(message));
setMessages([message, ...messages]);
}
All event handlers associated with the WebSocket goes inside the useEffect()
hook. The returned function serves the purpose of componentWillUnmount()
in the function component.
useEffect(() => {
ws.onopen = () => {
console.log('WebSocket Connected');
}
ws.onmessage = (e) => {
const message = JSON.parse(e.data);
setMessages([message, ...messages]);
}
return () => {
ws.onclose = () => {
console.log('WebSocket Disconnected');
setWs(new WebSocket(URL));
}
}
}, [ws.onmessage, ws.onopen, ws.onclose, messages]);
We put it all together inside a function component as shown below.
import React, { useState, useEffect } from "react";
const URL = 'ws://127.0.0.1:8080';
const Chat =() => {
const [user, setUser] = useState('Tarzan');
const [message, setMessage] = useState([]);
const [messages, setMessages] = useState([]);
const [ws, setWs] = useState(new WebSocket(URL));
const submitMessage = (usr, msg) => {
const message = { user: usr, message: msg };
ws.send(JSON.stringify(message));
setMessages([message, ...messages]);
}
useEffect(() => {
ws.onopen = () => {
console.log('WebSocket Connected');
}
ws.onmessage = (e) => {
const message = JSON.parse(e.data);
setMessages([message, ...messages]);
}
return () => {
ws.onclose = () => {
console.log('WebSocket Disconnected');
setWs(new WebSocket(URL));
}
}
}, [ws.onmessage, ws.onopen, ws.onclose, messages]);
return (
<div>
<label htmlFor="user">
Name :
<input
type="text"
id="user"
placeholder="User"
value={user}
onChange={e => setUser(e.target.value)}
/>
</label>
<ul>
{messages.reverse().map((message, index) =>
<li key={index}>
<b>{message.user}</b>: <em>{message.message}</em>
</li>
)}
</ul>
<form
action=""
onSubmit={e => {
e.preventDefault();
submitMessage(user, message);
setMessage([]);
}}
>
<input
type="text"
placeholder={'Type a message ...'}
value={message}
onChange={e => setMessage(e.target.value)}
/>
<input type="submit" value={'Send'} />
</form>
</div>
)
}
export default Chat;
We add minimal CSS to align the <ul/>
's' and the <li/>
's and designate some height to the messages area.
ul {
height: 150px;
padding-inline-start: 0px;
}
ul > li {
list-style-type: none;
}
and import it into the React file.
import './Chat.css';
Now, navigate to the location of the WebSocket server file Server.js
and start the chat.
node Server.js
Start the React application.
npm start
You can open two browser windows, fill the User
fields with two different names (Tarzan
and Jane
here) and start the chat.
You can proceed on your own from here: improve the UI, add timestamps to messages, display profile pictures, create databases for chat history, etc.
Notes
-
The WebSocket implemented on the client side is the WebSocket API, not the installed
ws
.