Alex Hoggett
Alex Hoggett

Reputation: 75

useEffect hook re-rendering twice in React & Socket.io

I'm in the process of building a real-time chat app using React and socket to get to grips with the basics. Whenever a user sends a message, all other users receive this same message twice.

I'm using react-router to serve a join 'page' and a chat 'page'.

function App() {
  return (
    <div className="App">

      <Router>
        <Routes>
          <Route path="/" element={<Join />} />
          <Route path="/chat/:name" element={<Chat />} />
        </Routes>
      </Router>

    </div>
  );
}

In the chat page, which is where the messages are being rendered between users, I'm using the useEffect hook however I am struggling to fully understand it.

function Chat () {
  const [messages, addMessage] = useState([{
    username: "chatbot",
    time: new Date(),
    message: "Welcome to the chatroom 😈"
  }]);

  const location = useLocation();
  const [currentUser, updateUser] = useState(() => {
    const user = location.pathname.split('/chat/')[1];
    socket.emit("join_chat", {
      username: user
    })
    return user;
  });

  useEffect(() => {
    socket.on("receive_message", (data) => {
      console.log('received message');
      addMessage(prevMessages => {
        return [...prevMessages, {
          username: data.username,
          time: new Date(),
          message: data.message
        }]
      })
    })
  }, [socket])

  const sendMessage = (e) => {
    e.preventDefault();
    socket.emit("send_message", {
      message: currentMessage,
      username: currentUser,
    });

The console log is run twice on the receiving side every time a message is sent.

From what I understand, the hook allows us to define some code that will be run whenever there is a change to the DOM, so I'm unsure where this change is happening twice?

I'm also unsure as to why the socket.on should live inside useEffect in the first place. If useEffect is only called immediately after a re-render, how is it still receiving every message via the socket?

Upvotes: 3

Views: 1375

Answers (1)

Drew Reese
Drew Reese

Reputation: 202676

From what you describe it sounds like you are rendering the app into a React.StrictMode component which runs certain lifecycle methods and functions twice as a way to help detect unintentional/unexpected side-effects.

The effect is run twice and the unintentional/unexpected side-effect is that the code has added two event handlers for the "receive_message" event.

The useEffect hook is missing returning a cleanup function to unsubscribe from the socket/message event when the component unmounts or the socket dependency changes.

useEffect(() => {
  const handleMessage = (data) => {
    console.log('received message');
    addMessage(prevMessages => {
      return [...prevMessages, {
        username: data.username,
        time: new Date(),
        message: data.message
      }]
    });
  };

  socket.on("receive_message", handleMessage);

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

From what I understand, the hook allows us to define some code that will be run whenever there is a change to the DOM

The useEffect hook allows you to define some code that will run as a side-effect whenever the component renders for any reason. Reasons React components rerender are (1) Local state was updated, (2) a React prop was updated, (3) the parent component rerendered (for any of the same reasons).

I'm also unsure as to why the socket.on should live inside useEffect in the first place.

The "render" method in React components is to be considered a pure function. The entire function body of a React Function component is the "render" method. Subscribing to external resources is considered an "impure" action, i.e. it's a side-effect, and should only be subscribed to as part of the regular React component lifecycle.

Upvotes: 2

Related Questions