React with Socket.IO Messaging App

Peter Le Development Technologies, Node.js, React 2 Comments

Attention: The following article was published over 2 years ago, and the information provided may be aged or outdated. Please keep that in mind as you read the post.

Building a messaging app sounds complicated, but it really isn’t. With the right tools, creating one might be simpler than you think. In this post, I will show you how to accomplish this task using Socket.IO.

Before we dive in, let’s get some expectations out of the way. There will not be anything fancy in this app like different channels, sender name, or message date-time. However, those can be added later if you want.

The main purpose of this post is to allow you to understand the basics of React and Socket.IO. Once you have the basics down, you’ll be all set to research and apply more advanced features on your own.

Here’s the basic of what we will be building in this post:

React and Socket.IO messaging application

Socket.IO

So what is this Socket.IO I’ve been mentioning?

Socket.IO is a cross-platform browser or device-enabling, real-time, event-based bidirectional communication. The creators have made socket.io super simple to use and implement. Hence, this application will utilize Socket.IO to accomplish our goal to build a messaging application.

You can visit Socket.IO to learn more.

Setup

For the messaging app we’re building, we are going to utilize:

  • Nodejs + Express as the server
  • React and Material-UI to build our UI components
  • Socket.IO package to connect our backend and frontend to finalize our project

Tools we will be using in this project include Nodejs, Git, and Yarn.

All set?

Let’s begin…

NodeJs + Express

First, initialize our server code by executing the following command.

npm init

Our main server file will be called server.js. Ensure you enter that as you set up a new node project on your machine.

Then, install the rest of the npm packages.

cd to the initialized node project.
yarn add express cors socket.io

React + Material-UI

If you didn’t know already, Material-UI is a library of React UI components. It’s based on Material Design to allow developers to build a faster React application. We will be using Material-UI in this project to build out a simple UI.

In your current project, run the following command to create the React app as a client folder.

npx create-react-app client
cd client
yarn add @material-ui/core clsx prop-types react socket.io-client

Implementation

Now that our project is set up, the next step is to begin coding the main part of our app.

First, we will create a server.js file in our root directory.

Server File

Within our server.js file, include the required packages.

const express = require("express");
const cors = require("cors");
const http = require("http");
const socketIO = require("socket.io"); 

Then, initialize Socket.IO and connect to our server.

const app = express();
const server = http.createServer(app);

const io = socketIO(server, {
  cors: true,
  origins:["localhost:3000"]
});

Next, set up a connection event listener between the server and the client. This will listen to new messages from the client and broadcast the new message to the other user(s) through the emit function. The data object contains the message as well as the sender ID to identify who sent the message.

io.on("connection", (socket) => {
  socket.join(room);
  
  socket.on(NEW_MESSAGE_EVENT, (data) => {
    io.in(room).emit(NEW_MESSAGE_EVENT, data);
  });

  socket.on("disconnect", () => {
    socket.leave(room);
  });
});

Here’s the completed server.js file:

const express = require("express");
const cors = require("cors");
const http = require("http");
const socketIO = require("socket.io"); 

// setup the port our backend app will run on
const PORT = 3030;
const NEW_MESSAGE_EVENT = "new-message-event";

const app = express();
const server = http.createServer(app);

const io = socketIO(server, {
  cors: true,
  origins:["localhost:3000"]
});

app.use(cors());

// Hardcoding a room name here. This is to indicate that you can do more by creating multiple rooms as needed.
const room = "general"

io.on("connection", (socket) =>; {
  socket.join(room);

  socket.on(NEW_MESSAGE_EVENT, (data) => {
    io.in(room).emit(NEW_MESSAGE_EVENT, data);
  });

  socket.on("disconnect", () =>; {
    socket.leave(room);
  });
});

server.listen(PORT, () => {
  console.log(`listening on *:${PORT}`);
});

That is it for our server.js file! Simple.

The remainder of our code will be on the UI, so let’s get to it.

Custom Hook

First of all, navigate to the client directory.

cd client

Second, create a new file called useChatRoom.jsx.

Since we are using the latest React version, we will be using hooks to create our UI. A custom hook, useChatRoom, is a React hook that will allow us to connect to the server and send our messages.

The main portion of our custom hook will be in the useEffect, which is setup up by a socket event listener. The event will ensure that messages being received are emitted by the server to the other user(s) of the app.

This step will utilize the socket.io-client package that we installed earlier.

useEffect(() => {
    // create a new client with our server url
    socketRef.current = socketIOClient(SOCKET_SERVER_URL);

    // listen for incoming message
    socketRef.current.on(NEW_MESSAGE_EVENT, (message) => {
      const incomingMessage = {
        ...message,
        isOwner: message.senderId === socketRef.current.id,
      };
      // send the new message to the others in the same room.
      setMessages((messages) => [...messages, incomingMessage]);
    });

    return () => {
      socketRef.current.disconnect();
    };
}, []);

Completed code for useChatRoom.jsx follows.

import { useEffect, useRef, useState } from "react";
import socketIOClient from "socket.io-client";

// this is the same event name as our server. This will allow communication between the server and client possible.
const NEW_MESSAGE_EVENT = "new-message-event"; 
const SOCKET_SERVER_URL = "http://localhost:3030";

const useChatRoom = () => {
  const [messages, setMessages] = useState([]);
  const socketRef = useRef();

  useEffect(() =>; {
    // create a new client with our server url
    socketRef.current = socketIOClient(SOCKET_SERVER_URL);

    // listen for incoming message
    socketRef.current.on(NEW_MESSAGE_EVENT, (message) => {
      const incomingMessage = {
        ...message,
        isOwner: message.senderId === socketRef.current.id,
      };
      // send the new message to the others in the same room.
      setMessages((messages) => [...messages, incomingMessage]);
    });

    return () => {
      socketRef.current.disconnect();
    };
  }, []);

  // send the messagee along with a sender id. The sender id would allow us to style the UI just like a message app like iOS.
  const sendMessage = (messageBody) => {
    socketRef.current.emit(NEW_MESSAGE_EVENT, {
      body: messageBody,
      senderId: socketRef.current.id,
    });
  };

  return { messages, sendMessage };
};

export default useChatRoom;

Chat Room

Finally, the app needs to allow the user to input and send messages.

Part of the following code represents styling for our Material-UI components.

This utilizes the owner/sender ID presented in the custom hook we created. Messages sent by the owner will be left-aligned and highlighted in blue. Otherwise, it will be green and right-aligned.

Like any React app, we start by setting up some state and utilizing our customer hook.

const { messages, sendMessage } = useChatRoom();
const [newMessage, setNewMessage] = useState("");

Next, we need a couple of event handlers to ensure we save new messages as well as an event handler to send the messages.

const handleNewMessageChange = event => {
  setNewMessage(event.target.value);
};

const handleSendMessage = () => {
  if (newMessage !== "") {
    sendMessage(newMessage);
    setNewMessage("");
  }
};

As for the input, we will use a basic text field and button from Material-UI in our return block.

<div className={classes.action}>
  <TextField
    className={classes.messageInput}
    id="message"
    label="Message"
    placeholder="enter message here"
    variant="outlined"
    value={newMessage}
    onChange={handleNewMessageChange}
    onKeyUp={handleKeyUp}
  >
  <Button
    disabled={!newMessage}
    variant="contained"
    color="primary"
    onClick={handleSendMessage}
    className={classes.sendButton}
  >
    Send
  </Button>
</div>

And this is the finalized code. As mentioned before, this final version includes some extra code to improve the user experience.

import React, {useRef, useState, useEffect} from "react";
import {Paper, TextField, Button, makeStyles} from "@material-ui/core";

import useChatRoom from "./useChatRoom";
import clsx from "clsx";

const useStyles = makeStyles({
  container: {
    display: "flex",
    alignItems: "center",
    justifyContent: "center",
    height: "100vh",
    backgroundColor: "#263238"
  },
  paper: {
    width: "50em",
    height: "80%",
    position: "relative"
  },
  action: {
    display: "flex",
    width: "96%",
    alignItems: "center",
    margin: "1em",
    position: "absolute",
    bottom: 0
  },
  sendButton: {
    width: "10em",
    height: "50%",
    margin: "0 2em"
  },
  messageInput: {
    width: "100%"
  },
  messageContainer: {
    overflowY: "auto",
    height: "85%"
  },
  divider: {
    margin: "0.1em"
  },
  message:{
    listStyle: "none"
  },
  owner:{
    margin: "1em",
    backgroundColor: "#0091EA",
    padding: "0.5em 1.5em",
    borderRadius: "20px",
    color: "#FFF",
    wordBreak: "break-word",
    maxWidth: "65%",
    width: "fit-content",
    marginRight: "auto"
  },
  guest: {
    margin: "1em",
    backgroundColor: "#8BC34A",
    padding: "0.5em 1.5em",
    borderRadius: "20px",
    color: "#FFF",
    wordBreak: "break-word",
    maxWidth: "65%",
    width: "fit-content",
    marginLeft: "auto"
  },
  ol: {
    paddingInlineEnd: "40px"
  }
});

const Room = () => {
  const { messages, sendMessage } = useChatRoom();
  const [newMessage, setNewMessage] = useState("");
  const classes = useStyles();
  const messageRef = useRef()

  const handleNewMessageChange = event => {
    setNewMessage(event.target.value);
  };

  const handleSendMessage = () => {
    if (newMessage !== "") {
      sendMessage(newMessage);
      setNewMessage("");
    }
  };

  // extra code to send the message as you press the enter key.
  const handleKeyUp = event => {
    if (event.key === "Enter"){
      if (newMessage !== "") {
        sendMessage(newMessage);
        setNewMessage("");
      }
    }
  }

  // allow scrolling to the bottom of the container when a new message arrived.
  useEffect(() => messageRef.current.scrollIntoView({behavior: "smooth"}))

  return (
    <div className={classes.container}>
      <Paper elevation={5} className={classes.paper}>
        <div className={classes.messageContainer}>
          <ol className={classes.ol}>
            {messages.map((message, i) => (
              <li
                key={i}
                className={clsx(classes.message, message.isOwner ? classes.owner : classes.guest)}
              >
                <span>{message.body}</span>
              </li>
            ))}
          </ol>
          <div ref={messageRef}></div>
        </div>
        <div className={classes.action}>
          <TextField
            className={classes.messageInput}
            id="message"
            label="Message"
            placeholder="enter message here"
            variant="outlined"
            value={newMessage}
            onChange={handleNewMessageChange}
            onKeyUp={handleKeyUp}
          />
          <Button
            disabled={!newMessage}
            variant="contained"
            color="primary"
            onClick={handleSendMessage}
            className={classes.sendButton}
          >
            Send
          </Button>
        </div>
      </Paper>
    </div>
  );
};

export default Room;

Lastly, we’ll just need to replace App with Room within index.js

Run the app

Alright, all things are coded. Now, it’s time to test it out!

Server

yarn start

UI

cd client
yarn start

At this point, you can open up localhost:3000 on a couple of tabs and start typing between the two tabs!

Conclusion

As I mentioned before, this is a very basic chat app. I just wanted to show you the basics of Socket.IO.

One of the advanced things you can do is to serve up live orders when someone ordered from your e-commerce site. I’ve seen this implemented on many sites where I see a popup message on a corner that said “X product was recently purchased in California.”

I’ll leave you to your imagination on how far you can take Socket.IO in your application!

As always, you can find the finished code on my Github repository at https://github.com/peterhle/react-socket-io.

0 0 votes
Article Rating
Subscribe
Notify of
guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments