Reputation: 4484
I'm using websocket in react. This is the code for the component:
import React, { useState, useEffect } from "react";
export default function Component(props) {
const [socket, setSocket] = useState();
const parseMessage = (msg) => {
if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket.
};
const sendMessage = (msg) => socket.send(msg); // error at this line
useEffect(() => {
const socket = new WebSocket("wss://ws.ifelse.io/");
socket.addEventListener("message", ({ data }) => {
if (socket) parseMessage(data);
});
setSocket(socket);
}, []);
const sendMsg = () => {
socket.send("test");
};
return <button onClick={() => sendMsg("clicked")}>send msg</button>;
}
I'm getting this error: TypeError: Cannot read properties of undefined (reading 'send')
at the marked line. The WebSocket is just an echo server, it sends back the same thing you send it.
If I wrap the socket.send
in a try-catch block, I can still send and receive messages from the WebSocket using the button, but the error still occurs at that line in sendMessage
.
It's clear that the socket variable is not undefined as I'm able to send and receive messages before and after the error occurs.
So my question is why is the socket variable undefined only for the brief period after receiving a message, and what is the fix for this issue.
Upvotes: 2
Views: 2611
Reputation: 209
You need to wait for the socket to be established first and then set the socket using setSocket in the open
event. The way you are doing it now expects the first message to be sent by the server and then you setSocket, if the server does not send a message before you click on the button, the socket instance is not set in your state.
Do this instead:
socket.addEventListener('open', function (event) {
setSocket(socket);
});
Here is the full code
import React, { useState, useEffect } from "react";
export default function Component(props) {
const [socket, setSocket] = useState();
const [disabled, setButtonDisabled] = useState(true);
const parseMessage = (msg) => {
if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket.
};
const sendMessage = (msg) => socket.send(msg); // error at this line
useEffect(() => {
const socket = new WebSocket("wss://ws.ifelse.io/");
socket.addEventListener('open', function (event) {
setSocket(socket);
setButtonDisabled(false);
});
socket.addEventListener("message", ({ data }) => {
if (data) parseMessage(data);
});
return ()=>{
socket.close();
};
}, []);
const sendMsg = () => {
if(socket) socket.send("test");
};
return <button disabled={disabled} onClick={() => sendMsg("clicked")}>send msg</button>;
}
Upvotes: -1
Reputation: 3942
The useEffect
runs just once and in that moment the socket
is still undefined. Function sendMessage
references undefined socket
when the effect runs. When the socket is set using setSocket
, component will rerender, but the new instance of sendMessage
(now referencing the existing socket) will never be used, because the effect will not run again.
It is better to use ref in this case.
export default function Component(props) {
const socket = useRef();
const sendMessage = (msg) => socket.current.send(msg);
useEffect(() => {
socket.current = new WebSocket("wss://ws.ifelse.io/");
socket.current.addEventListener("message", ({ data }) => {
parseMessage(data);
});
return () => {
... release resources here
}
}, []);
...
}
Upvotes: 1
Reputation: 11930
Better solution would be to initialize the socket outside
import React, { useState, useEffect } from "react";
const socket = new WebSocket("wss://ws.ifelse.io/")
export default function Component(props) {
const ws = useRef(socket)
useEffect(() => {
ws.current?.addEventListener("message", ({ data }) => {
parseMessage(data);
});
return () => {
ws.current?.removeAllListeners()
}
}, [])
const parseMessage = (msg) => {
if (msg[0] !== "R") sendMessage("123"); // ignore the very first message from the socket.
};
const sendMessage = (msg) => ws.current?.send(msg);
const sendMsg = () => {
ws.current?.send("test");
};
return <button onClick={() => sendMsg("clicked")}>send msg</button>;
}
Upvotes: 4
Reputation: 1018
When you use useState
you create a state variable with an update state function. You also provide the initial value of the state variable in useState(initialState)
.
In your case you are not passing anything (i.e. you are telling React it's undefined
, that's why at this line:
const sendMessage = (msg) => socket.send(msg); // error at this line
your code cannot use socket.send
because socket is undefined
.
Your useEffect
runs after the return function and then the socket instance is created as well as set to socket state variable.
You need to make sure that you are only sending message over socket after socket is created.
For more on useEffect
you can read this post:
https://jayendra.xyz/2019/06/17/how-useeffect-works/
Upvotes: -1