1

I'm writing a chat client using socket.io and react hooks. The message history (chatMessages) when addMessage (upon receiving the message event) is called is incorrect (always an empty array). chatMessages does update (correctly), but on the next call it is empty. There shouldn't be any other variable named chatMessages and messages is in the parent class (the set hook works so I don’t see why the state should be invalid). The state doesn't appear to update again either, so it appears to something in add messages.

import React, { useState, useCallback, useEffect } from "react";
import "./styles/chat.css";
import { uuidv4 } from "./utils/utils";
const SERVER_URL = "http://localhost:3337";
const io = require("socket.io-client");


function ChatManager(props) {
  // How to handle sockets (where do we store them)
  const [UId, setUId] = useState("");
  const [socket, setSocket] = useState(null);
  const [chatMessages, setChatMessages] = useState([]);
  const [role, setRole] = useState("Role");
  const useForceUpdate = () => useState()[1];
  function displayMessage(msg) {
    if (UId) {
      if (msg.user === UId) {
        return (
          <article class="msg-container msg-self" id="msg-0">
            <div class="msg-box">
              <div class="flr">
                <div class="messages">
                  <p class="msg" id="msg-0">
                    {msg.message}
                  </p>
                </div>
              </div>
            </div>
          </article>
        );
      } else {
        return (
          <article class="msg-container msg-remote" id="msg-0">
            <div class="msg-box">
              <div class="flr">
                <div class="messages">
                  <p class="msg" id="msg-0">
                    {msg.message}
                  </p>
                </div>
              </div>
            </div>
          </article>
        );
      }
    }
  }

  function addMessage(msg) {
    console.log("message: ");
    console.log(msg);
    const newMessages = [...chatMessages];
    console.log(chatMessages);
    newMessages.push(msg);
    console.log(newMessages);
    setChatMessages(newMessages);
    console.log("Message added");

  }

  useEffect(() => {
    console.log("did mount");
    if (!socket) {
      setUId(uuidv4());
      console.log("Set UId");
      setSocket(io.connect(SERVER_URL));
      console.log("Socket mounted");
    } else {
      console.log("Socket emit");

      socket.emit("join", {
        uid: UId,
        tid: "Task ID, add later",
        role: role
      });

      socket.on("error", function(err) {
        console.log("received socket error:");
        console.log(err);
      });

      socket.on("message", function(msg) {
     addMessage(msg);
      });
    }
    return () => {
      console.log("Unmounting");
      if (socket) {
        socket.emit("leave", {
          uid: UId,
          tid: "Task ID, add later",
          role: role
        });
        console.log("Leaving message sent");
      }
    };
  }, [socket]);

  function sendMessage() {
    const msg = "Test message";
    socket.emit("usr_input", {
      uid: UId,
      tid: "Task ID, add later",
      message: msg,
      role: role
    });

    console.log("Message sent");
  }

return (
    <div>
      <h1> Here is the chatbox </h1>
      <ChatBox 
      sendMessage={sendMessage} 
      chatMessages={chatMessages}
      displayMessage={displayMessage}/>
      <h1> EOF </h1>
    </div>
  );
}

function ChatBox(props) {
  return (
    <section class="chatbox">
      <section id="chat-messages" class="chat-window">
        {props.chatMessages.map( (msg) => {props.displayMessage(msg)})}
      </section>

    <form class="chat-input" onsubmit="return false;">
        <input
          type="text"
          id="message"
          autocomplete="on"
          placeholder="Type a message"
        />
        <button type="button" onClick={props.sendMessage}>
          <svg
            style={{
              width: "24px",
              height: "24px"
            }}
            viewBox="0 0 24 24"
          >
            {" "}
            <path
              fill="rgba(0,0,0,.38)"
              d="M17,12L12,17V14H8V10H12V7L17,12M21,16.5C21,16.88 20.79,17.21 20.47,17.38L12.57,21.82C12.41,21.94 12.21,22 12,22C11.79,22 11.59,21.94 11.43,21.82L3.53,17.38C3.21,17.21 3,16.88 3,16.5V7.5C3,7.12 3.21,6.79 3.53,6.62L11.43,2.18C11.59,2.06 11.79,2 12,2C12.21,2 12.41,2.06 12.57,2.18L20.47,6.62C20.79,6.79 21,7.12 21,7.5V16.5M12,4.15L5,8.09V15.91L12,19.85L19,15.91V8.09L12,4.15Z"
            />{" "}
          </svg>{" "}
        </button>
      </form>

Any ideas or workarounds would be greatly appreciated!

norbitrial
  • 14,716
  • 7
  • 32
  • 59
Felix Labelle
  • 153
  • 3
  • 15
  • 1
    Did you figure this out? I'm running into the same issue and can't work out why. It's almost like a clojure issue but that doesn't make sense as the function in question is not memoized so should be updating itself. – Hemal Feb 17 '21 at 05:44
  • 2
    @norbitrial's answer worked for my use case, I would recommend that. – Felix Labelle Feb 17 '21 at 17:33
  • Does this answer your question? [React hooks functions have old version of a state var](https://stackoverflow.com/questions/55066697/react-hooks-functions-have-old-version-of-a-state-var) – Matt Jensen Jun 19 '23 at 09:07

2 Answers2

4

What about if you mutate the previous state of your chatMessages?

As the useState hook functional updates' documentation states:

If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.

Maybe you can try as the following code snippet:

function addMessage(msg) {
  setChatMessages(previousMessages => {
    return [
      ...previousMessages,
      msg
    ];
  });
}

I hope that helps!

norbitrial
  • 14,716
  • 7
  • 32
  • 59
  • 1
    It’s odd that it doesn’t work without mutating the state, but I’ll look into it another time. Thanks for your help! – Felix Labelle Jan 02 '20 at 16:42
-1

I guess your value is capture, have a look the following like for more detail.

Steps to fix it:

  1. create a ref with const myFuncRef = useRef(null)
  2. set ref.current to you function with myFuncRef .current = myFunc
  3. call with func ref onClick={val => myFuncRef .current(val)}

How to get the previous props or state?

How React Hooks Components Capture Values (And How To Use the Latest Values Instead)

ygweric
  • 1,002
  • 1
  • 7
  • 22