Chat App Using React and Socket.io Hanging After Sending Too Many Messages

Viewed 1323

My basic chat App gets extremely slow after sending like 7 or so messages, although it does work when only a few messages are sent.

I put a print statement inside the returned component. I noticed each time I submit a chat message, the print statement is called twice as many times as before. I assume my app is getting slow due to having to re-render the DOM exponentially more times as I send more messages.

Does anyone have any ideas on why this is happening?

This is my React App

import React from 'react';
import io from "socket.io-client";
let socket = io.connect("localhost:8080");

function App() {
  const [curMessage, setMessage] = React.useState({name: "", message: ""});
  const [messageList, setList] = React.useState([]);

  socket.on("receiveMsg", message => {
    setList([...messageList, message]);
  });

  function handleTyping(e) {
    let target = e.target;
    if (target.id === "name") {
      setMessage({...curMessage, name: target.value});
    } else {
      setMessage({...curMessage, message: target.value});
    }
  }

  function handleSubmit(e) {
    e.preventDefault();
    setList([...messageList, curMessage]);
    setMessage({name: "", message: ""});
    socket.emit("message", curMessage);
  }



  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input id="name" onChange={handleTyping} value={curMessage.name}></input>
        <input id="message" onChange={handleTyping} value={curMessage.message}></input>
        <button>Send Message</button>
      </form>
      <ul>
        {messageList.map(msg => {
          return <li> {msg.name} {msg.message} </li>
        })}
        {console.log("test")}
      </ul>
    </div>
  );
}

export default App;

This is my Server Side Code (Though I assume this code is fine)

const express = require("express");
const app = express();
const http = require("http").createServer(app);
const io = require("socket.io")(http);

http.listen(8080, () => console.log("Connected to Server"));

io.on("connection", socket => {
  console.log("User has connected " + socket.id);
  socket.on("message", data => {
    socket.broadcast.emit("receiveMsg", data);
  });
});

3 Answers

Try to put your socket.on(..) inside useEffect. Maybe performing setState inside rendering was the cause of slowing.

useEffect(() => {
  socket.on("receiveMsg", (message) => {
    setList([...messageList, message]);
  });

  return () => {
    socket.off("receiveMsg");
  };
}, [messageList]);

Answer given by Chanandrei have a slight pitfall. Lemme explain.

FIRST LETS SEE WHY YOUR CODE HAVING PROBLEM ?


You have placed socket.on listener directly inside your component rather then placing it inside any hook. It means each time your component re renders a new socket.on listener gets created.
  • Your component is mounted -> You will have one socket.on listener
  • You receive 1st message -> Previous Listener is still there but due to component re rendering a new Listener will be created
  • You receive 2nd message -> Previous two listener combined will now result in total of 4 listeners

And this pattern continues ..

You might be tempted to use code given below, BUT ITS WRONG APPROACH.

useEffect(() => {
  socket.on("receiveMsg", (message) => {
    setList([...messageList, message]);
  });

  return () => {
    socket.off("receiveMsg");
  };
}, [messageList]);

PROBELM WITH USING [messageList] AS SECOND ARGUMENT TO useEffect ??

The thing is you do not want to create a new socket.io listener each time your messageList is updated. You should always create just one listener for one event during the lifetime of a component. Because once socket.on listener is created then it is not destroyed after listening to first occurance of that event, it just again listens for that event.

So such Listeners must be created once. In your case it should be created when component mounts.

Hence use [], empty list as second argument to useEffect. This hook is equivalent to componentDidMount.

CORRECT CODE

useEffect(() => {
  socket.on("receiveMsg", (message) => {
    setList([...messageList, message]);
  });

  return () => {
    socket.off("receiveMsg");
  };
}, []);

With this code you will get a warning that you need messageList as dependecy in the useEffect

useEffect(() => {
  socket.on("receiveMsg", (message) => {
    setList([...messageList, message]);
  });

  return () => {
    socket.off("receiveMsg");
  };
}, []);

You only need to make a functional update like this

setList((prevMsgs) => [...prevMsgs, message]);
Related