12 React Websocket. Problems with useEffect with arrays and concatenated strings

1. Explanation of this example

In this example, the process is the following:

  1. the client (React) sends a request to the server.
  2. The server receives the request and while processing, it sends messages informing the client about the process
  3. The last message sent to the client is "close-me", which means that the server has finished its work
  4. When the client receives any message, this message is stored both in a string and in an array
  5. When the client receives the message "close-me", then it closes the connection.

2. Problems with variables

1. Inside the "useEffect", the variables in an assignment must be all in "useState." As inside a "useEffect" code, the variables that don't use "useState" are initialized.

2. Don't use "useEffect" for detecting changes in arrays or strings whose value is concatenated ( for instance setMyVar(myVar + aString) )

3. Taking care of non-open connections

If you want to send a message, the connection must be open. Here is an example of function that wait before sending a message from StackOverflow

/**
 * Execute a websocket function when the state is open
 * @param ws 
 * @param callback 
 * @param interval 
 */
export function wsWaitForConnection (ws:WebSocket, callback:Function, interval:number)  {
    if (ws.readyState === 1) {
        callback();
    } else {
        // optional: implement backoff for interval here
        setTimeout(function () {
            wsWaitForConnection(ws, callback, interval);
        }, interval);
    }
};

4. The Component

The previous function is used in the call to send a message 

import { TextareaAutosize } from "@mui/material";
import React, {useEffect, useState} from "react"
import { useSearchParams } from "react-router-dom";
import GetIconByName from "../../utils/GetICon";
import { getI18nLabelByCode } from "../i18n/Funcs"
import { i18nMapLogin, i18nMapMain } from "../i18n/i18nFields"
import {getProp} from '../props/props'
import { wsWaitForConnection } from "../websockets/wsutils";

export default function ActionView2 () {

    
    const [searchParams] = useSearchParams()
    let langCode = searchParams.get('langCode')
    let idMenu = searchParams.get('idMenu')
    let mnuLabel = searchParams.get('mnuLabel')
    let mnuIcon = searchParams.get('mnuIcon')
    let mnuType = searchParams.get('mnuType')
        
    let webSocket:WebSocket 
    let socketUrl:string
        
    const [aMsg, setAMsg] = useState('')
    const [myMsg, setMyMsg] = useState('hola:')
    const [arrayMsg,  setArrayMsg ] = useState<string[]>([])
    
    let arrMsg:string[]=[]
    const [conta, setConta] = useState(0)
                
    function processOpen (event: Event) { 
        const str1="Server Connect...."
        arrMsg.push(str1)
        setArrayMsg(arrMsg)
        setAMsg(str1)
        console.log('01. Connect + ' +str1 )
    }
    
    function processMessage(message: MessageEvent<string>) { 
        const str1=message.data
        arrMsg.push('02.RECEIVE:'+str1) 
        setArrayMsg(arrMsg)
        setAMsg(str1)
        console.log("02. Proces...."+ str1 + "-->" + JSON.stringify(arrMsg)  )
        if (str1==="close-me") {
            console.log("Closing 01...")
            
            webSocket.close()
            console.log("02.1 Proces.... closing "+ str1 + "-->" + JSON.stringify(arrMsg)  )	
        }
    }	

    function processClose(event: Event) {
        if (webSocket) {
            const str1="Server Close...."
            arrMsg.push(str1)
            setArrayMsg(arrMsg)
            setAMsg(str1)
            
    	    console.log("03. Close...."+ str1 + "-->" + JSON.stringify(arrMsg)  )
            console.log("03.1 Close...."+ str1 + "-->" + JSON.stringify(arrMsg)  )
        }    
    }

    function processError(event: Event) {
        const str1="Error...."
        setAMsg(str1)
        arrMsg.push(str1)
        setArrayMsg(arrMsg)
        console.log("04. Error...."+ str1 )
        
    }

    function sendMessage(message: string) {
        if (webSocket) {
    	    arrMsg.push('05.SEND:'+ message)
    		webSocket.send(message);
            console.log("05. Send...."+ message)
        }     
    }

    useEffect(() => {
        if (searchParams) {
            langCode = searchParams.get('langCode')
            idMenu = searchParams.get('idMenu')
            mnuLabel = searchParams.get('mnuLabel')
            mnuIcon = searchParams.get('mnuIcon')
            mnuType = searchParams.get('mnuType')
        }
      }, [searchParams]);  

      useEffect(() => {
        if (aMsg) {
            setMyMsg(JSON.stringify(arrayMsg))
            
        }
      //}, [conta,WSState]);  
        }, [aMsg]);  
     
    const handleExecute = (event:React.MouseEvent<HTMLButtonElement>) => {    
      
        console.log("Selecteeeeeeeeeeeed :" + idMenu + ' label=' + mnuLabel + ' icon='+ mnuIcon +" URL="+ getProp('webSocketURL') + idMenu); 
        arrMsg=[]        
        socketUrl =getProp('webSocketURL') + idMenu
        webSocket = new WebSocket(socketUrl)
        //setWSState(true)
        if (webSocket) {
            webSocket.onopen =    function (event: Event)                  { processOpen(event);};
            webSocket.onclose =   function (event: Event)                  { processClose(event);};
            webSocket.onerror =   function (event: Event)                  { processError(event);};
            webSocket.onmessage = function (message: MessageEvent<string>) { processMessage(message);};
            setConta(conta+1)
            wsWaitForConnection(webSocket, () => sendMessage("Begin "+ conta),100) 
             
            
        }   
    }

    return (
        <div>
            
            <p><GetIconByName name={mnuIcon ? mnuIcon : 'fa-blind'} size='2x'/> {mnuLabel}</p>
            <button type="button" className="btn btn-secondary" onClick={handleExecute}>
                {getI18nLabelByCode('execute', langCode ? langCode : 'ca', i18nMapMain)}
            </button>
            
            <button type="button" className="btn btn-secondary">
                {getI18nLabelByCode('cancel' , langCode ? langCode : 'ca', i18nMapLogin)}
            </button>
            
            <p>{aMsg}</p>
            <p>===========================================</p>
            
            <ul>
                {arrayMsg.map((message, idx) => (
                    <li key={idx}>{message ? message : null}</li>
                ))}
            </ul>
            <p>===========================================</p>
                        
            <TextareaAutosize
                id='myActionView2TxtArea'
                minRows={4}
                aria-label="Respuestas"
                placeholder="."
                value={myMsg}
                style={{ width: 400 }}
            />
            <button type="button" className="btn btn-secondary">
                {getI18nLabelByCode('ok', langCode ? langCode : 'ca', i18nMapMain)}
            </button>
        </div>
    )   
}



The displayed window is











Comentarios

Entradas populares de este blog

15. Next.js Tutorial (2). Fetching data. Async functions getStaticProps, getServerSideProps, getStaticPaths

14. Next.js Tutorial (1)

10. React deployment on Nginx (5). Passing client certificate DN to react client