Websocket is undefined immediately after receiving message in react - javascript

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.

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>;
}

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
}
}, []);
...
}

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/

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>;
}

Related

How to prevent socketIO from emitting duplicate data in React Js

When I'm emitting message from client side.
I'm receiving lot of same duplicated message
on server side or in my message displaying container div here is the code
Global Socket Provider Context
A simple context which provide socket to all children or components
// fileName : SocketProvider.js
import { createContext } from "react";
import { io } from "socket.io-client";
const socket = io("http://localhost:8080/", { withCredentials: true });
const SocketContext = createContext();
export const SocketProvider = ({ children }) => {
return (
<SocketContext.Provider value={{ socket }}>
{children}
</SocketContext.Provider>
);
};
export default SocketContext;
And I've wrapped SocketProvider in App.js like this
<SocketProvider>
<Main/>
</SocketProvider>
In Dashboard Page or Main Page
Where I've div for displaying messages and input message box whenever user press enter or click send button which is img I'm emitting a message here is the code
// filename : Main.jsx
// all things imported correctly
import ...
const Main = ()=> {
const { socket } = useContext(SocketContext);
const [message, setMessage] = useState("");
const [conversation, setConversation] = useState([]);
// another global context
const { currentUserId } = useContext(UserContext);
const { selectedUserId, setSelectedUserId } = useContext(SelectedUserContext);
// code for changing selectedUserId
// ...
...
.... //
// on start sending userJoined
useEffect(() => {
socket.emit("userJoined", {
senderUserId: currentUserId,
receiverUserId: selectedUserId,
});
}, [socket, currentUserId, selectedUserId]);
// useEffect for listening messages
useEffect(() => {
const onMessageReceived = (msg) => {
setConversation((conversations) => [...conversations, msg]);
};
socket.on("message", onMessageReceived);
return () => {
socket.off("message", onMessageReceived);
};
}, [socket]);
// this function will send message
const sendMessage = useCallback(() => {
if (!message) return;
socket.emit("message", {
msg: message,
sentBy: "Unknown"
});
setMessage("");
}, [socket, message]);
// on enter btn pressed
useEffect(() => {
const handleEnterKeyPressed = (e) => {
if (e.keyCode === 13 && e.shiftKey === false && e.ctrlKey === false)
sendMessage();
};
element.addEventListener("keydown", handleEnterKeyPressed);
return () => {
element.removeEventListener("keydown", handleEnterKeyPressed);
};
}, [sendMessage]);
return (
<div className="container">
<div className="msgContainer">
{conversation.map((data)=>((
<h1 className="...">{data.msg}</h1>
<p className="...">{data.sentBy}</p>
)}
</div>
<input
value={message}
onChange={(e)=>setMessage(e.target.value)}
placeholder="Enter message here"/>
<img className="..." src={sendIcon} onClick={sendMessage}>
</div>
);
}
export default Main;
Code In Server Side Where I'm Listening for message
//filename: server.js
io.on("connection", (socket) => {
socket.on("userJoined", (userJoinedData) => {
const chatRoomId = generateChatRoomId(userJoinedData);
socket.on("message", async (msgData) => {
// this console log many times or more than 11
console.log(`DEBUG: New Message Arrived :`, msgData);
// saving msg to mongo db
const savedMessage = await saveMsg(msgData);
io.to(chatRoomId).emit("message", savedMessage);
});
});
});
and even I got following warning in terminal :
(node:14040) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 message listeners added
to [Socket]. Use emitter.setMaxListeners() to increase limit
Any help will be always appreciated :)
As #NickParsons mentioned -
Is that peice of code being executed multiple times?
Whenever currentUserId or selectedUserId changes you are emiting userJoined in server code. And userJoined listener on server side get called and your message listener listen again your data so that's why you are getting duplicated data.
To fix this you have to place message listener outside of userJoined listener!
I know you are getting some info of userJoinedData so that's why you have place message listener inside userJoined.
So you have to also pass that userJoinedData while emitting message in your client side and change code in your server side like this -
//filename: server.js
io.on("connection", (socket) => {
socket.on("userJoined", (userJoinedData) => {
const chatRoomId = generateChatRoomId(userJoinedData);
// .. your other codes
});
// but place message listener outside here
socket.on("message", async (msgData, userJoinedData) => {
// now here also you can generate chatRoomId
const chatRoomId = generateChatRoomId(userJoinedData);
// saving msg to mongo db
const savedMessage = await saveMsg(msgData);
io.to(chatRoomId).emit("message", savedMessage);
});
});

Updating value from UDP socket using svelte store

I am trying to update a variable whenever I receive data from udp. The connection is working fine and when I send some data I am getting it in the console correctly. But seems the variable is not being updated. I want to use the variable from a svelte component that will update in the browser. i am using sveltekit.
import { writable } from 'svelte/store';
import dgram from 'dgram';
const Socket = () => {
const server = dgram.createSocket({type:"udp4",reuseAddr:true});
server.bind(4444);
const temperature = writable(0);
const getTemperature = () => {
return temperature;
}
server.on('message', (msg, rinfo) => {
var data = JSON.parse(msg);
temperature.set(data.message);
console.log(temperature);
});
return { getTemperature };
};
export default Socket;

My useState in React/NEXTjs is not appending the result from a socket.io event from the server, just overwriting it

Frameworks used: Next.js, Socket.io, React
I am making a simple messaging app. I am basically just emitting a message someone types, sending that message to the server, which is then "broadcasted" back through an event called "receive-chat-message". The issue I'm having is when the response is handled on the front end with "receive-chat-message", the [messages] state is not appending a new message to display, it's just overwriting the state.
My goal is to send a message to the server; the server then sends the message back to the client. I then append the new message on the client, sent from the server, to the [messages] state on the client, and then render the state of [messages]. The problem is it's only overwriting the [messages] state, and not appending new messages to it.
Code that sends message to the server
const [message, setMessage] = useState("");
const handleChange = (e) => {
setMessage(e.target.value);
};
const submitMessage = async () => {
// socket.io
const socket = io();
socket.emit("send-chat-message", message, user);
setMessage("");
};
Front-End Code
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = io();
socket.on("receive-chat-message", handleSetMessage);
return () => socket.disconnect();
}, []);
const handleSetMessage = (data) => {
/* data prop is the new message - it has a {message: "message", user: "name"}
sent from server.
THIS IS THE PROBLEM IN THE CODE WHERE IT DOESN'T APPEND
NEW MESSAGES TO THE [messages] STATE, IT JUST OVERWRITES
THE CURRENT STATE WITH THE NEW MESSAGE. */
setMessages([...messages, data]);
};
Back-End Code
export default async function handler(req, res) {
if (!res.socket.server.io) {
const io = new Server(res.socket.server);
res.socket.server.io = io;
io.on("connection", async (socket) => {
socket.on("send-chat-message", async (message, user) => {
socket.broadcast.emit("recieve-chat-message",
{
message,
name: user && user.name,
});
});
});
}
}
I solved this by setting messages through the functional method like:
setMessages((messages) => [...messages, data]);
Instead of:
setMessages([...messages, data])
I think we need this because I was updating the state from a function called within a socket.io listener and was never actually grabbing the previous state; so I had to direct it to the previous state and then merge the values.

web3js intercepting when metamask connects manually

Currently I have the following code to connect the metamask wallet in the browser (using reactjs and web3js):
function App() {
const [contract, setContract] = useState();
const [account, setAccount] = useState();
const[web3Obj, setWeb3Obj]=useState();
useEffect(()=>{
async function load(){
const web3 = new Web3(Web3.givenProvider || 'http://http://localhost:7545');
setWeb3Obj(web3);
const accounts = await web3.eth.requestAccounts();
console.log(accounts[0] + " is the account");
setAccount(accounts[0]);
$.get( "getcontractaddress")
.done( function( data ) {
const _contract = new web3.eth.Contract(abi, data);
_contract.address = data;
console.log(_contract.address + " is the contract");
setContract(_contract);
});
}
load();
},[])
return(
);
}
I deleted the return(); part from the snippet because its irrelevant to the question.
Whenever I switch to a different account in metamask, the "account" object is not being updated. How can I intercept the event of metamask switching accounts to automatically reset the account object to the new account?
Your component does not know when you switch accounts in Metamask. To detect account changes, Metamask provides the accountsChanged event.
ethereum.on('accountsChanged', handler: (accounts: Array<string>) => void);
You can add the event to your load function or make another useEffect. You can also remove the event when your component unmounts using ethereum.removeListener.
useEffect(() => {
const { ethereum } = window;
if (ethereum && ethereum.on) {
const handleAccountsChanged = (accounts) => {
console.log("accountsChanged", accounts);
setAccount(accounts[0])
};
ethereum.on('connect', handleConnect);
return () => {
if (ethereum.removeListener) {
ethereum.removeListener('accountsChanged', handleAccountsChanged);
}
};
}
}, []);

Client to retrieve data from backend server database table via a websocket connection

I am using the following server code to retrieve data from a postgres db:
const express = require('express')
const app = express()
const server = require('http').createServer(app);
const pool = require("postgresql");
const WebSocket = require('ws');
const wss = new WebSocket.Server({ server:server });
const getTempData = async () => {
try {
const tempData = await pool.query("select country, temp from my_temp_table");
return JSON.stringify(tempData.rows)
} catch(err) {
console.error(err.messasge);
}
}
wss.on('connection', async (webSocketClient) => {
console.log('A new client Connected!');
const tempDetails = await getTempData();
webSocketClient.send(tempDetails);
webSocketClient.on('message', (message) => {
console.log('received: %s', message);
});
});
server.listen(3000, () => console.log(`Listening on port :3000`))
Now on the client side, I have created the following websocket connection to localhost 3000.
When first rendering the below client code, the data displays where I also get all the console log messages, i.e. ws opened, getting data.... and finally console logging the actual data.
isPaused is also set to false.
The problem I'm facing and unsure what the issue is, is that I expected to see my client page update the country/temp data (no page refresh), when I updated the country/temp values in my_temp_table database table, but it didn't.
The result that I expected was that via the websocket, anytime my table on the server-side updated, the client would update the tempData, via the second useEffect hook below.
I basically would like the client to pull in and display changes from the server via websocket when the data changes in the backend db table.
import React, { useState, useEffect, useRef } from 'react';
export default function Temperature() {
const [isPaused, setPause] = useState(false);
const [tempData, setTempData] = useState([]);
const [name, setName] = useState(null);
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket("ws://localhost:3000");
ws.current.onopen = () => {
console.log("ws opened");
}
ws.current.onclose = () => console.log("ws closed");
return () => {
ws.current.close();
};
}, []);
useEffect(() => {
if (!ws.current) return;
ws.current.onmessage = e => {
if (isPaused) return;
console.log("getting temp data....");
const data = JSON.parse(e.data);
setTempData(data)
console.log("data: ",data);
};
}, [isPaused]);
return (
<div>
<button onClick={() => setPause(!isPaused)}>
{isPaused ? "Resume" : "Pause"}
</button>
{ tempData?
tempData.map((data, i) => (
<div>
<span>{data.country}</span>
<span>{data.temp}</span>
</div>
))
: null }
</div>
)
}
The code is executing only once because there are no recurrying calls to the web socket send event. When the web socket is created it gets the data from the database and sends it, and thats it.
You probably want some kind of action that triggers this event multiple times. For example, in your code:
wss.on("connection", async webSocketClient => {
console.log("A new client Connected!");
setInterval(() => {
const timeNow = Date.now();
webSocketClient.send(
JSON.stringify([
{ country: "country-a", temp: timeNow },
{ country: "country-b", temp: timeNow },
])
);
}, 1000);
webSocketClient.on("message", message => {
console.log("received: %s", message);
});
});
I see you are using some package to pool from a PostgreSQL db. Take a look at this other example.
How would your clients know if there is any change in database on server side ?
You can create an event that triggers each time a particular data changes and listen to those event on your client sockets. Like you did with onmessage event in your current code.
You can render the react component based on this event.

Categories

Resources