1

I am getting data in streams from a websocket url. I am using Rxjs for websocket and i have to concatenate those string streams into sentences. But my react state does not update after each messsage received. As a result only the else block is firing in my case. Can somebody help me with this, im open to alternate solutions as well. Thanks in advance. Here is the code:-

import { useContext, useEffect, useState } from "react";
import { Context } from "../context/Context";

function Home() {
  const { subject, getWelcomeMessage } = useContext(Context);
  const [messages, setMessages] = useState([]);
  const [currentMessage, setCurrentMessage] = useState(null);
  const [previousMessage, setPreviousMessage] = useState(null);

  useEffect(() => {
    console.log("currentMessage", currentMessage);
    console.log("messages", messages);
  }, [currentMessage, messages]);

  useEffect(() => {
    subject.subscribe({
      next: (val) => {
        console.log("Val", val);
        if (
          val.m == "$$$EOM$$$" ||
          val.m == "$$$EOM:CRITICAL$$$" ||
          val.m == "$$$EOM:ERROR$$$"
        ) {
          console.log("&& Error");
          const tempCurrentMessage = { ...currentMessage };

          if (tempCurrentMessage) {
            tempCurrentMessage.inProgress = false;
          }
          setPreviousMessage(tempCurrentMessage);
          setCurrentMessage(null);
        } else if (currentMessage !== null) {
          console.log("Current Message is not null");
          const tempCurrentMessage = { ...currentMessage };
          tempCurrentMessage.inProgress = true;
          let temp = [...currentMessage.content];
          temp.push(val.m);
          tempCurrentMessage.content = temp;

          setCurrentMessage(tempCurrentMessage);
        } else {
          console.log("Current Message is null");
          const tempCurrentMessage = { role: "assistant", content: [val.m] };
          setCurrentMessage(tempCurrentMessage);

          const tempMessages = [...messages];
          tempMessages.push(tempCurrentMessage);
          setMessages(tempMessages);
        }
      },
    });
    getWelcomeMessage();
  }, []);

  return (
    <div>
      <h3>Batty Chat Bot</h3>
    </div>
  );
}

Home.propTypes = {};

export default Home;

sage_OMEGA
  • 73
  • 4
  • 1
    What if you add `currentMessage` as a dependency to the `useEffect` where you subscribe to the subject? Also, you should make sure you unsubscribe from the subject when the component unmounts. – Andrei Gătej Aug 13 '23 at 18:52

1 Answers1

0

It is not working because your subscription callback function is capturing only the initial value of currentState. There are several ways to solve this.

1 - Use a mutable ref

// ...
const [currentMessage, setCurrentMessage] = useState(null);

// create a mutable reference that you will keep updated to the current value
// access the current value in your callback through this reference
const currentMessageRef = useRef()
currentMessageRef.current = currentMessage

// ...
useEffect(() => {
  const s = subject.subscribe({
    next: val => {
      // access the most recent value through the ref object
      const currentMessage = currentMessageRef.current
      // rest of your code unchanged
    }
  })
  return () => s.unsubscribe()
}, [subject])

// ...

See my other answer here for more details on how this can work.

2 - Use multiple states

This method can be just as effective. The idea is to store the websocket message as state and do your logic outside of any effect function closure.

// will hold the most recent message from the socket
const [wsMessage, setWsMessage] = useState()

// holds the previous message so we can detect when it changes
const [prevWsMessage, setPrevWsMessage = useState()

// ... your other init code


// handle the logic when the message has been received
if (wsMessage !== prevWsMessage) {
  setPrevWsMessage(wsMessage)

  // your logic to update currentMessage and other state goes here
  // ...
}

// subscribe and just store the messages into our wsMessage state
useEffect(() => {
  const s = subject.subscribe({next: setWsMessage})
  return () => s.unsubscribe()
}, [subject])
Brandon
  • 38,310
  • 8
  • 82
  • 87