'Why is my socket.io event listener works properly on a UI component but doesn't work on the other in React?
I have a weird bug that I'm trying to solve (or maybe I made it weird because I'm new to React). I'm using React and TypeScript to build an app that will allows me to subscribe to certain topics in order to receive values and show me in the UI in real time.
Briefly, I have an array containing topics and I'm looping through this array to dynamically create UI elements:
{selectedTopics.map((topic: any, idx: number) =>
< Slider key={idx}
topic={topic}
socket={socket} // socket.on is working on this component
selectedTopics={selectedTopics}
setSelectedTopics={(topics: any) => setSelectedTopics(topics)}
/>)
}
<CustomComponent socket={socket} /> // socket.on is not working on this component
As you can see I'm creating a global socket connection in my root app and passing it to children as prop. Then, I'm calling props.socket.on('subscribe', callback); In the child component.
Here is my code for the child component (Slider):
const Slider: React.FC<{
socket: SocketIOClient.Socket;
topic: any;
selectedTopics: any;
setSelectedTopics: any;
}> = (props) => {
const [realTimeValue, setRealTimeValue] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [subscribed, setSubscribed] = useState<boolean>(false);
const onSubscribe = (topic: any) => {
props.socket.on("onSubscribe", (data: any) => {
const name = data.Name;
const payload = data.Value;
console.log(`received response with name: ${name} and payload: ${payload}`);
if (topic.name === name) {
console.log(`topic received= ${name} correspond to tpic name = ${topic.name}`);
setSubscribed(true);
setRealTimeValue(payload);
setLoading(false);
}
else {
console.log(`received topic = ${name} but current was ${topic.name} `);
}
});
}
const subscribe = (topic: any) => {
/* I'm invoking this function with a click button. So when the user clicks, then this function will be triggered.
*/
const req = {
subscribe: topic.Topic,
}
props.socket.emit('subscribe', req);
setLoading(true);
onSubscribe(topic);
}
.
.
.
As you can see, I'm doing this because I need to send the event to the server where I specify the topic name in order to say to the server, hey I want to subscribe this specific topic. I don't know if this is the best approach since I'm calling the same event and same callback function for all UI components.
This works perfectly, this is not my issue. My issue is I'm passing this global socket object also to the next UI component, which is the CustomComponent. Here is a simplified version of my code:
import React, { useState, useEffect } from 'react';
import { IonLabel} from '@ionic/react';
const Custom: React.FC<{
socket: SocketIOClient.Socket;
}> = (props) => {
const [temp, setTemp] = useState("");
const handle = (data: any) => {
console.log(`**received ${data}`);
setTemp("received");
}
useEffect(() => {
console.log("useEffect called ..")
console.log(`height: ${window.innerHeight} | width: ${window.innerWidth}`);
props.socket.on('onCustom', handle);
}, []) // I tried passing temp, setTemp and props.socket in the dependencies array but nothing worked. However, this works when I pass the handle function in the dep array or not pass any array at all. But not quite as expected because it renders many times until my app freezes.
return (
<IonLabel>{temp}</IonLabel>
);
};
export default Custom;
As you can see, I'm using useEffect hook to call socket.on when the component mounts, since I want it to run when the component mounts and not get triggered by a button like in the Slider component.
Now this does not work and I'm not able to consume the messages sent from the server. It's like my props.socket.on is not working, which means that it is not listening on the incoming response from the server. Surprisingly, this works when I don't pass an array dependency at all or when I pass the handle function in the dependency array. However, this renders thousand times per second and after a while the app freezes and I can't do anything.
Edit
I'm creating my socket instance outside the app component so that it will not render all the time. I don't know if this is the appropriate way to do it. So I have something like this:
const socket = socketIOClient(serverHost);
const App: React.FC = () => {
.
.
.
.
As you can see I'm creating the socket outside and them I'm using it in the app component by passing it to the children components.
Solution 1:[1]
I'm not sure if this will work. Try to move the handle function inside useEffect callback and add the temp in the depedency:
useEffect(() => {
console.log("useEffect called ..")
console.log(`height: ${window.innerHeight} | width: ${window.innerWidth}`);
props.socket.on('onCustom', (data: any) => {
console.log(`**received ${data}`);
setTemp("received");
});
}, [temp])
Solution 2:[2]
I had the same problem in Angular, The problem was because the chatSocketService provider was a submodule To correct I put the service which extend the socket in the main module of the project
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Chanandrei |
| Solution 2 | Samba |
