DEV Community

Nardin
Nardin

Posted on

Real-time notifications with React and Socket-IO

Lately I have been trying to implement a notification system for my React application using the socket.io library. I want to share what I learned about it and also my implementation.
This is a simple example. Let's begin :)

These are the things you can get to know from this article.

  1. Why do we need to use socket-io?
  2. What is socket-io?
  3. How to use socket-io in React and Node applications?
  4. Implementing a simple chatroom app

I want to implement a simple chat application where you can login just by entering your name and write a message and send it to every other user that is online in the application.

The question is, how can we get all messages in real-time without refreshing the page?
The first approach that may come to mind is to use the Rest API and send requests to fetch new messages every few minutes. This process is known as polling, and it is not really a good approach for scenarios like this. Why?
First, assume you have no new messages but send requests every minute to know if there are any. Second, you are not able to get a new message just in time, and it makes the user experience worse or less enjoyable.

Another approach and better one is somehow making the server send new messages without getting a request from the user. If we can do this, we don't need to send new requests every minute, and it has a better user experience. Here, socket-io can help us.

What is socket-io?

To answer this question first, you should know about WebSocket.

WebSocket

The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.

Socket.IO is a JavaScript library that provides a higher-level abstraction on WebSocket, making it easier and faster to develop real-time functionality compared to using raw WebSocket.

The main idea behind socket.io is that you can send and receive any events you want, with any data you want.
Here is a simple implementation of how to use socket-io in React in the front-end and express in the back-end.

front-end
Make sure you have React and socket.io-client installed on your machine. If not try these commands:

npx create-react-app my-app --template typescript
cd my-app
npm install socket.io-client
npm start
Enter fullscreen mode Exit fullscreen mode

In App component when a user set her username, inside 'loginHandler' function we create a connection to server using socket-io and user emit an event which is named 'hello-server'.
On the other side, the server will listen to this event and has its own callback function to occur whenever this event happens.
Here is the App component:

import { useState } from "react";
import { io, Socket } from "socket.io-client";
import Login from "./Login";
import MessageBox from "./MessageBox";

const WS_URL = "http://localhost:5000";

interface ServerToClientEvents {
  "new-message": (message: string, username: string) => void;
}

interface ClientToServerEvents {
  "hello-server": (usernameInput: string) => void;
  "send-message": (message: string, username: string) => void;
}

export type MySocket = Socket<ServerToClientEvents, ClientToServerEvents>;

const App = () => {
  const [username, setUsername] = useState<string>("");
  const [socket, setSocket] = useState<MySocket>();

  const loginHandler = (usernameInput: string) => {
    setUsername(usernameInput);
    const socket: MySocket = io(WS_URL);
    setSocket(socket);

    socket.emit(`hello-server`, usernameInput);
  };

  return (
    <div className="w-full h-screen flex flex-col items-center overflow-scroll">
      <div className="p-2 bg-orange text-white m-2 rounded-2xl">
        {username === "" ? "Ofline" : username}
      </div>
      {username === "" ? (
        <Login onLogin={loginHandler} />
      ) : (
        <MessageBox socket={socket!} username={username} />
      )}
    </div>
  );
};

export default App;

Enter fullscreen mode Exit fullscreen mode

In the Login component, we simply enter our username and submit it.
./Login.tsx

import { useState } from "react";

const Login = ({ onLogin }: { onLogin: (usernameInput: string) => void }) => {
  const [inputValue, setInputValue] = useState("");

  const submitHandler = () => {
    if (inputValue === "") return;
    onLogin(inputValue);
  };

  return (
    <>
      <label className="p-4">PLease enter your username to login:</label>
      <input
        type="text"
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
        className="border-[0.1rem] border-gray-dark p-4"
      />
      <button
        onClick={submitHandler}
        className="bg-gray-dark text-white py-2 px-4 m-4 hover:text-orange"
      >
        Login
      </button>
    </>
  );
};

export default Login;
Enter fullscreen mode Exit fullscreen mode

When a user sends a new message, the socket tells the server that there is a new notification by emitting a "send-message" event, and all we need to do is listen to this event, just like the 'hello-server' event in the App component, and the server emits a new event in response to this event ("send-message" event), which is "new-message", and the client also listens to this event in the 'MessageBox' component.

./MessageBox.tsx

import { useEffect, useState } from "react";
import { MySocket } from "./App";

const MessageBox = ({
  socket,
  username,
}: {
  socket: MySocket;
  username: string;
}) => {
  const [inputMessage, setInputMessage] = useState<string>("");
  const [messagesFromServer, setMessagesFromServer] = useState<string[]>([]);

  useEffect(() => {
    socket.on("new-message", (message: string, username: string) => {
      const newMessage = `${username}: ${message}`;
      setMessagesFromServer((pre) => [newMessage, ...pre]);
    });

    return () => {
      socket.close();
    };
  }, [socket]);

  const sendMessageHandler = (event: React.MouseEvent<HTMLButtonElement>) => {
    event.preventDefault();
    if (inputMessage === "") return;

    socket.emit("send-message", inputMessage, username);
    setInputMessage("");
  };

  return (
    <>
      <div className="w-full h-4/5 p-4 overflow-scroll">
        <div className="text-gray-dark font-bold py-4">Chat room</div>
        {messagesFromServer.map((message, index) => (
          <div
            key={index}
            className="bg-gray-dark text-white w-fit px-4 py-1 rounded-2xl my-2"
          >
            {message}
          </div>
        ))}
      </div>

      <div className="fixed bottom-1 left-0 w-full flex flex-row justify-between px-4">
        <input
          type="text"
          value={inputMessage}
          onChange={(e) => {
            setInputMessage(e.target.value);
          }}
          className="w-full border-[0.1rem] rounded-3xl outline-none p-4 border-gray-dark"
        />
        <button
          onClick={sendMessageHandler}
          className="w-16 p-2 rounded-3xl  bg-gray-dark text-white hover:text-orange"
        >
          Send
        </button>
      </div>
    </>
  );
};

export default MessageBox;
Enter fullscreen mode Exit fullscreen mode

back-end
In server side make sure you have node, socket-io and express installed on your machine. If not try this command:

npm install node express socket.io-client
Enter fullscreen mode Exit fullscreen mode

Let's initialize a new instance of socket.io by passing the server (the HTTP server) object, listen on the 'connection' event for incoming sockets.
And also listen to the events that the client may emit, which are "hello-server" and "send-message".

import express from "express";
import { createServer } from "http";
import { Server, Socket } from "socket.io";

interface ServerToClientEvents {
  "new-message": (message: string, username: string) => void;
}

interface ClientToServerEvents {
  "hello-server": (usernameInput: string) => void;
  "send-message": (message: string, username: string) => void;
}

type WSServer = Server<ClientToServerEvents, ServerToClientEvents>;

type WSSocket = Socket<ClientToServerEvents, ServerToClientEvents>;

const app = express();

const server = createServer(app);
const ws: WSServer = new Server(server, {
  cors: {
    origin: ["http://localhost:3000", "https://admin.socket.io"],
  },
});

ws.on("connection", (socket: WSSocket) => {
  console.log("Connection established");

  socket.on("hello-server", (username: string) => {
    console.log(`${username} say hello`);
  });

  socket.on("send-message", (message: string, username: string) => {
    console.log(message);
    ws.emit("new-message", message, username);
  });
});

server.listen(5000);
console.log("Listening on port 5000");
Enter fullscreen mode Exit fullscreen mode

You can find the full app on my GitHub.
https://github.com/nardinsy/chatroom-socket-io-demo

Top comments (0)