Search code examples
reactjswebsocket

Storing async WebSocket messages in React state without message loss


I have a React app that connects to a WebSocket using react-use-websocket. Messages are received asynchronously, processed by receiveHandler and then the socketMessages state is updated with the response. Sample code:

import useWebSocket from 'react-use-websocket';

const [socketMessages, setSocketMessages] = useState([])
const { lastJsonMessage } = useWebSocket(path);
    
useEffect(() => {
  const processedResponse = receiveHandler(lastMessage)
  setSocketMessages([...socketMessages, processedResponse])
}, [lastMessage]);

const receiveHandler = (lastMessage) =< {
   // do receive processing on lastMessage
   return processedLastMessage
}

My issue is that messages are sometimes arriving in quick succession and new state updates are happening before existing updates are finished, causing messages to get lost.

Is there a simple way to handle this within the native functionality? Or, do i need a module to implement some form of queue?


Solution

  • I think you can achieve that using UseRef() and setTimeOut() with function to check if your value is updated.

    This should do the purpose:

    import React, {useState, useEffect, useRef} from 'react';
    import useWebSocket from 'react-use-websocket';
    //
    const WebSocketComponent = ({path}) =>{
       //
       const [socketMessage, setSocketMessage] = useState([]);
       const {lastJsonMessage} = useWebSocket(path);
       const messageQueue = useRef([])
       const processingQueue = useRef(false)
       //
       const recieveHandler = (message) =>{
       // process the received message
       // implement your message processing logic here ..
       return processedMessage;
       }
       
       
       const processQueue = () => {
          if(processingQueue.current || messageQueue.current.Length ===0){
             return;
          }
          processQueue.current = true;
          const processNextMessage = () => {
             if(messageQueue.current.Length === 0){
                processingQueue.current = false;
                return;
             }
             //
             const nextMessage = messageQueue.current.shift();
             const processedMessage = receiveHandler(nextMessage);
             //
             setSocketMessage((prevMessage)=>[...prevMessages, processedMessage]);
              // schedule the next message to be processed after the state update: 
             setTimeout(processNextMessage, 0);
          };
     
          processNextMessage();
       };
       
       useEffect(()=>{
          if(lastJsonMessage){
             messageQueue.current.push(lastJsonMessage);
             processQueue();
          }
       },[lastJsonMessage])
    
    }
    

    I didn't run the code in vsc so please let me know if it worked.