This article explain what the framework "Socket.io" means and Create a simple Group Chat App with React. Here is the link to the GitHub repository. Feel free to comment on any issue, I will always be available to reply.
Goals
The Aim of this tutorial is to explain how Socket.io V4 works and simplify the use of it with a front-end framework like React
Table of Content
- Prerequisites
- Getting Started
- Setting up the Server
- Setting up React
- Connect Client to Server
- Creating the Server connection
- Refactoring React App
- Creating Routes
- Connecting the React to the Server
- Handling CORS
- Connecting to Different Rooms
- Messaging
- Welcome Message
- Sending Message
- Disconnect
- Conclusion
Prerequisites
- ES6 Syntax
- Knowledge on React and Node
- A Text Editor, Visual Studio Code or Atom preferably
- NodeJS Installed already
What is Socket.io?
Socket.io is a JavaScript Library that allows bi-directional Secured Realtime communication between the browser and the server. Which means that if a user sends a data, the recipient(s) of that data would receive immediately, depending on the internet speed.
How it Works
According to Socket.io, The client will try to establish a WebSocket connection if possible, and will fall back on HTTP long polling if not. WebSocket Establishes the connection between the client and the server. Socket.io makes use of this connection the WebSocket brings to transfer data.
Let's Jump deep into the course of this article.
Getting Started
Open up the terminal in your desired folder, then Create a new folder and move into it:
mkdir react-chat-app
cd react-chat-app
npx create-react-app .
Navigate back to the projects root folder, initialize the project and install server dependencies:
npm init -y
npm i express socket.io concurrently nodemon
Concurrently helps in running more than command at the same time without creating another terminal. This would really help in running both our react and server side together in one terminal.
Nodemon is a tool that automatically restarts the server when changes are made to the file directory.
Setting up the Server
After all installations are complete, we create a server.js
file in the projects root directory and require all necessary dependency:
const http = require("http");
const express = require("express");
Setting up our server for socket.io would not be the same as our normal express setup. According to socket.io documentation, we create our set up socket.io using node http
server:
const app = express()
const server = http.createServer(app)
const io = socketio(server)
const PORT = process.env.PORT || 5000
server.listen(PORT, () => console.log(`Server is Quannected to Port ${PORT}`))
The constant PORT
makes use of ES modules that checks if our app is deployed. If the app is not deployed, it would return 5000.
We need to add few lines of code to our script
tag inside the package.json
file, to enable us run our server using npm
:
"start": "node server.js",
"server": "nodemon server",
"dev": "concurrently \"npm run server\" \"cd client && npm start\""
Let's try out our app in our terminal:
npm run dev
Setting Up React
Move into react-chat-app
and let's open up our terminal to install the dependencies we would be utilizing in this article:
npm i react-router socket.io-client query-string react-router-dom
Socket.io-client is a dependency created by socket.io to help connect to socket.io in the server.
Query-string helps us get parameter in our url
from the address bar.
Connect Client to Server
This is where the our messaging app starts. Here, we would create a socket.io connection between our react app with our server app.
Creating the Server Connection
A listening event has to be made in the server.js
for client to connect to the server:
io.on("connection", (socket) => {
console.log('A Connection has been made')
socket.on('disconnect', ()=> {
console.log('A disconnection has been made')
})
})
The constant io
is listening for a connection
from the client and when that connection made is, it creates a special socket for that particular connection. The socket, which is passed as a parameter in the arrow function, holds the properties of the connection which has just been made. In our Code, the socket
,which is the connection, listens for when it has been disconnected. And then socket is been removed since there has been a disconnection.
Refactoring React App
Before we can connect to the server, there are some refactoring we need to do to our new React app.
First we need to delete some of the pre-created file in our React app. Delete every thing in the src
folder and create index.js
in that same src
folder. Add the following code to the index.js
:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
To prevent react from yelling at us, we need to create the App.js
in the same directory with the index.js
. We need to add a functional component to our App.js that would return a simple welcome message:
import React from "react";
const App = () => {
<h1>App Successfully rendered.</h1>
}
export default App;
Creating Routes
Let's create a folder named components
in the src
, this would contain all our different component in our React app. In that components
folder, create a Home.js
and a Chat.js
file. When all has been created, navigate back to app.js
to set up our routes:
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from "./components/Home";
import Chat from "./components/Chat";
const App = () => (
<Router>
<Route path="/" exact component={Home}/>
<Route path="/chat" component={Chat} />
</Router>
);
export default App;
For clarity, the
Home
andChat
file is in thecomponents
and thecomponents
is in thesrc
. Thecomponents
has as
at the the end the word,take notes so you don't encounter an error 💗.
We created a route, that makes use of the functional component Home
when accessing the homepage and the Chat
when accessing the Chat page.
The Home
component would contain a form that would redirect us to the Chat
component of the specified group. Open up the Home.js
file and set up our form:
import React, { useState } from "react";
import { Link } from "react-router-dom";
const Home = () => {
const [name, setName] = useState("");
const [room, setRoom] = useState("");
return (
<div>
<h1>Home Page</h1>
<div>
<input
placeholder="Name"
type="text"
onChange={(event) => setName(event.target.value)}
required
/>
</div>
<div>
<input
placeholder="Room"
type="text"
onChange={(event) => setRoom(event.target.value)}
required
/>
</div>
<Link
onClick={(e) => (!name || !room ? e.preventDefault() : null)}
to={`/chat?name=${name}&room=${room}`}
>
<button type="submit">
Sign In
</button>
</Link>
</div>
);
};
export default Home;
To keep this article as short as possible, it would not be containing any style. You can add custom style if you prefer.
We imported useState
to hold the user's inputted name and room in a State value. Read more on useState.
In all the input tags, we had an onChange
event that listens for a change in input value and save it in the state
. We made use of the Link
, imported from react-router-dom
, to redirect us to the Chat-page (passing name
and room
as a parameter) if and only if our name
and room
State Variable has a value.
Connecting the React to the Server
We have set up our form, the next step is to create a connect and a disconnect from the server in our chat.js
:
import React, { useState, useEffect } from "react";
import queryString from "query-string";
import io from "socket.io-client";
let socket;
const Chat = ({ location }) => {
const [name, setName] = useState("");
const [room, setRoom] = useState("");
const ENDPOINT = "http://localhost:5000";
useEffect(() => {
const { name, room } = queryString.parse(location.search);
socket = io(ENDPOINT);
setRoom(room);
setName(name);
}, [location.search]);
return <div>Chat</div>;
};
export default Chat;
The App.js
file passed down a prop to Chat.js
,location
, using react-router-dom
and this location
prop contains the url
. Then we got the parameters (name and room) from the url
using the query-string
dependency and set them to a State Variable. The useEffect
runs every time location.search
changes value. Read more on useEffect
.
Handling CORS
In the useEffect
block, we created an instance of socket
and passed in our Server's Endpoint http://localhost:5000
. This would cause a breach in the Cross Origin Resource Sharing CORS
Policy since we are trying to data between two different routes.
Since
Socket.io
V3, we need to explicitly enableCORS
in our server to ensure the client successfully connect to the server.
Don't Panic🙂, We need to create options
in the server.js
Socket.io connection to permit the connection from the client. Since we have already declared the constant io
, we just need to add the options to the connection:
const io = require("socket.io")(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
allowedHeaders: ["my-custom-header"],
credentials: true,
},
});
Connecting to Different Rooms
We have to create a receiver in the server, waiting to receive new connection from the client. Create a new file user.js
, in the same directory with our server's file,that would be in charge of managing our users:
let users = [];
exports.addUser = ({ id, name, room }) => {
if (!name || !room) return { error: "name and room required." };
const user = { id, name, room };
users.push(user);
return { user };
};
The users
variable would contain all the users connected. We returned error if name or room is blank, else we would add the user to the array users and return the user.
We have to Create a listening event for client to join different room in our server.js
:
const {addUser} = require('./user')
io.on("connection", (socket) => {
socket.on("join", ({ name, room }, callBack) => {
const { user, error } = addUser({ id: socket.id, name, room });
if (error) return callBack(error);
socket.join(user.room);
callBack(null);
});
//The rest of the code
socket.on
listens for any connection from our client with the name "join"
then expects name
and room
as a parameter from the client. The callback sends an error if any or it would just return null, *There must return * from the server.
We need to connect to the event join
from the client and emit the inputted name and room as a parameter to the server.
useEffect(() => {
// The rest of the code
socket.emit("join", { name, room }, (error) => {
if (error) alert(error);
});
}, [location.search]);
Messaging
Alright, Here we are😮.
Welcome Message
We have to emit a welcome message to the user when the user joins a room.
All our messages would be coming from the server. When the user sends a message, we have to first send that message to the server then send it back to the client. The server would emit the message and the client would receive it.
Navigate to chat.js
to create the connection:
const [messages, setMessages] = useState([]);
useEffect(() => {
socket.on("message", (message) => {
setMessages((messages) => [...messages, message]);
});
}, []);
We created another useEffect that receives all messages from the server and set them to messages
state variable.
We need to render the messages on for the user in the return block. We need to use JSX to render all messages to the user:
return (
<div>
{messages.map((val, i) => {
return (
<div key={i}>
{val.text}
<br />
{val.user}
</div>
);
})}
</div>
);
We mapped through the messages
state variable, we specified the key as the index to avoid react
error and we returned the text
and user
passed down from our server.
Let's Connect to the connection created by our client from our server.js
:
io.on("connection", (socket) => {
socket.on("join", ({ name, room }, callBack) => {
//The rest of the code
socket.emit("message", {
user: "Admin",
text: `Welocome to ${user.room}`,
});
// The rest of the code
We are emitting to message
connection and we are passing the user
and the text
as parameters.
We also have to tell other users in the group that a new user has joined. Navigate to server.js
:
socket.broadcast
.to(user.room)
.emit("message", { user: "Admin", text: `${user.name} has joined!` });
The client is always listening for an emit
to message
. The message
is like the name or an identification for the connection.
The code we just wrote is broadcasting to other users in the room, telling them that a new user has just Joined the Group.
Sending Message
This is how sending of messages would be, We will get the message input from the user, send it to the server and then the server emits that message to everyone in the Group. Let's Open our chat.js
and create the input
field:
const handleSubmit = (e) => {
e.preventDefault();
if (message) {
socket.emit("sendMessage", { message });
setMessage("");
} else alert("empty input");
};
return (
<div>
// The rest of the code
<form action="" onSubmit={handleSubmit}>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<input type="submit" />
</form>
</div>
);
We are emitting to a new socket
event. It takes the message from the user and sends it to the newly created socket event sendMessage
. After we've emitted the message to the server using sendMessage
event, Open your server.js
and let's create the connection for the sendMessage
event:
socket.on("join", ({ name, room }, callBack) => {
//The rest of the code
socket.on("sendMessage", ({ message }) => {
io.to(user.room).emit("message", {
user: user.name,
text: message,
});
});
});
After we got the message from the client, we emitted that received message to everyone in the group.
Disconnect
This is last part of this article. After the user is done with chatting and would love to disconnect, we would have send a message to every one in the group, informing them that a user has just disconnected. Let's open our user.js
file and create a function that would be in charge of removing users from the array:
exports.removeUser = (id) => {
const index = users.findIndex((user) => user.id === id);
return users[index];
};
The function removeUser
would request for an id
, find a user with that id and then returns that user.
We have to import removeUser
in our server.js
and emit a disconnection message to every one in the returned user
group:
const { addUser, removeUser } = require("./user");
io.on("connection", (socket) => {
// The rest of the code
socket.on("disconnect", () => {
const user = removeUser(socket.id);
console.log(user);
io.to(user.room).emit("message", {
user: "Admin",
text: `${user.name} just left the room`,
});
console.log("A disconnection has been made");
});
});
When you restart your server, you might get an error when you refresh the Chat page. The best thing to do is to login again from the Home page
Conclusion
Congratulations, We've Successfully Created a Realtime Chat App with React
and Socket.io
.
Here is the link to the GitHub repository. I almost forgot to Shout out to one of my best YouTubers He was a great help in this article.
Thanks for staying with me till the end💗. If you like to deploy this Chat app to Heroku, I have an article where I talked about Deploying React and Node app to Heroku.
Till We Cross Path, I remain, Fredrick Emmanuel (divofred)😁😁❤❤
Top comments (9)
Why did you give credit to the original author of this project from youtube.
can u share me the link of that video
I added it in the conclusion section.
Video
why there is nothing in react folder on github?😤
I'm so sorry. I would push the react folder again.
Thanks
The react folder should be filled now 😁😁
This is great: easy for beginners to understand and full of content. Thanks for this!
No problem
Really liked the explanation, great post @divofred