How to Build a Realtime Group Chat Application with React and Socket.io
Tags: javascript, react, socketio, beginners
Published: 2021-11-08 13:54:17

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](https://github.com/divofred/ChatTutorial). 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](https://reactjs.org/ "What is React") and [Node](https://nodejs.org/en/about/ "What is Node")
- A Text Editor, [Visual Studio Code](https://code.visualstudio.com/download) or [Atom](https://atom.io/download) preferably
- [NodeJS](https://nodejs.org/en/download/) 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](https://socket.io/docs/v4/), The client will try to establish a [WebSocket](https://developer.mozilla.org/en-US/docs/Web/API/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:

```bash
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:

```bash
npm init -y
npm i express socket.io concurrently nodemon
```

[Concurrently](https://www.npmjs.com/package/concurrently "What is 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](https://nodemon.io/) 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:

```js
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](https://socket.io/docs/v4/server-initialization/) documentation, we create our set up socket.io using node `http` server:

```js
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`: 

```json
    "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:

```bash
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:

```bash
npm i react-router socket.io-client query-string react-router-dom
```

[Socket.io-client](https://socket.io/docs/v4/client-initialization/) 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:

```js
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`:

```jsx
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](https://www.freecodecamp.org/news/functional-components-vs-class-components-in-react/#:~:text=Functional%20components%20are%20basic%20JavaScript,mainly%20responsible%20for%20rendering%20UI.) to our App.js that would return a simple welcome message:

```jsx
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:

```jsx
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` and `Chat`  file is in the `components` and the `components` is in the `src`. The `components` has a `s`  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: 

```jsx
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](https://blog.logrocket.com/a-guide-to-usestate-in-react-ecb9952e406c/). 

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`:

```jsx
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.
![CORS ERROR![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9cog93uj3zr50vz7dlhi.png)
 ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k9rwabomht50jrxcwb49.png)
 

> Since `Socket.io` V3, we need to explicitly enable `CORS` 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:

```js
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:

```js
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`:

```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.

```js
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.<br/>

Navigate to `chat.js` to create the connection:

```jsx
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.<br/>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:

```jsx
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.<br/>Let's Connect to the connection created by our client from our `server.js`:

```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`:

```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. <br/>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:

```jsx
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:

```js
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:

```js
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. <br/>We have to import `removeUser` in our `server.js` and emit a disconnection message to every one in the returned `user` group:

```js
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](https://github.com/divofred/ChatTutorial). I almost forgot to Shout out to one of my [best YouTubers](https://m.youtube.com/watch?v=ZwFA3YMfkoc&t=182s) 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](https://dev.to/divofred/deploy-a-react-and-node-app-to-heroku-cgk).
Till We Cross Path, I remain, Fredrick Emmanuel (divofred)😁😁❤❤