Joshua
Joshua

Reputation: 41

React state resets with Socket.io

I am currently struggling with issues pertaining to socket.io and React. Whenever socket.on() is called, the state resets, and all the previous chat data is gone. In other words, whenever the user receives a message, the 'messages' state is reset.

I saw a similar post on this Socket.io resets React state?, but I couldnt seem to apply the same solution to my issue. Any help would be appreciated!

function ChatComponent(props) {
  const [messages, setMessages] = useState([]);
  const [socket, setSocket] = useState(socketioclient("*********"));
  function socket_joinRoom(room) {}

  function _onMessageUpdate(message) {
    setMessages([
      ...messages,
      {
        author: "them",
        type: "text",
        data: { text: message },
      },
    ]);
  }

  useEffect(() => {
    socket_joinRoom(parseInt(props.props[0], 10));

    socket.on("updateMessage", (message) => {
      //** When this is called, the state resets*
      console.log(messages);
      _onMessageUpdate(message);
    });
    return () => {
      socket.off("updateMessage");
      socket.disconnect();
    };
  }, []);

  function _onMessageWasSent(message) {
    setMessages([...messages, message]);
    socket.emit("sendMessage", message.data.text);
  }

  return (
    <div className="chatComponent" style={{ height: "100%" }}>
      <Launcher
        agentProfile={{
          teamName: `Ongoing: Room #${props.props[0]}`,
        }}
        onMessageWasSent={_onMessageWasSent}
        messageList={messages}
        isOpen={true}
        showEmoji
      />
    </div>
  );
}

Upvotes: 0

Views: 1517

Answers (2)

JohnyClash
JohnyClash

Reputation: 658

I'm answering this for other people searching along the terms of " why does socket.io reset my state when .on or other event listeners are used.

This turned out to be a simple useEffect() behavior issue and not an issue within socket.io. Although this is the case I feel that socket.io made an oversight not communicating this in their react hooks documentation.

Below is problem code.

useEffect(() => {
 socket_joinRoom(parseInt(props.props[0], 10));

 socket.on("updateMessage", (message) => {
  //** When this is called, the state resets*
  console.log(messages);
  _onMessageUpdate(message);
 });
 return () => {
  socket.off("updateMessage");
  socket.disconnect();
 };
}, []);

The above useEffect runs a single time at the mounting of this component. One may deduce that the variables used in writing this useEffect would remain dynamic and run once executed by socket.io, however it is not dynamic, it is static and the useState called in 'update_message' remains static and at the value it was during the mounting of the parent component.

To reiterate your component mounts and because of [] at the the end of useEffect the useEffect runs a single time at component mount. There is abstraction within socket.io in which the callback function ran by .on('update_message') is saved and the variable values are frozen at the time of mount.

This obviously seems like your state is being reset however that is not the case. You are just using the out dated states.

Like the other poster responded if you have a socket.io listener that needs to change dynamically based on variables or states that update you need to write a separate use effect with a dependency based on the state you wish to remain dynamic, you MUST also return a socket.off('updateMessage') so that the old listener is removed with the old state, everytime your state is updated. If you don't do this, you may also get old out of date states

useEffect(()=>{
  socket.on('updateMessage', (message)=>{
  onMessageUpdate(message)
})
return ()=>{
  socket.off('updateMessage')
},[messages])

Upvotes: 1

Aly Abd El Rahman
Aly Abd El Rahman

Reputation: 281

first of all you need to separate the joining room logic in it's own useEffect

useEffect(()=>{
        socket_joinRoom(parseInt(props.props[0],10));
},[props.props[0]])

cause you are going to listen to the changes of the received messages in another useEffect so you don't need to init the joining logic every time you receive anew message

and for the message function it can be another useEffect which listen to the received "messages" as this following code

useEffect(()=>{
   socket.on('message',(message)=>{
   setMessages((currentMessages)=>[...currentMessages,message])
}
},[])

this useEffect will fire and listen to the changes and add the new message to the previous messages

Upvotes: 1

Related Questions