12 React Websocket. Problems with useEffect with arrays and concatenated strings
1. Explanation of this example
In this example, the process is the following:
- the client (React) sends a request to the server.
- The server receives the request and while processing, it sends messages informing the client about the process
- The last message sent to the client is "close-me", which means that the server has finished its work
- When the client receives any message, this message is stored both in a string and in an array
- 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
Publicar un comentario