socket.on listen too much - reactjs

I created a socket in store (socket.store.ts)
constructor() {
this.socket = io.connect(process.env.REACT_APP_SOCKET_URI || '3131');
}
Using that socket in my component. When user click find button, socket emit one time (checked) to backend, backend emit one time (checked) back to event 'finding'. But called printed into console 12 times and even increase rapidly when i re-click find button
const socketStore = useContext(SocketStoreContext);
const socket = socketStore.socket;
// THOUGHT: maybe i will try to create socket inside this component
// const socket = io.connect(process.env.REACT_APP_SOCKET_URI);
socket.on('finding', () => {
console.log('called');
});
const handleFindPartner = () => {
socket.emit('find', {
token: user.token,
});
};
But when I create socket inside my component, called printed exactly one time. Don't know what going on here, really need some help. Thank you you guys so much!

Like #Mike said in comment section. I need to prevent socket.on from re-rendering by putting it in useEffect
const socketStore = useContext(SocketStoreContext);
const socket = socketStore.socket;
// THOUGHT: maybe i will try to create socket inside this component
// const socket = io.connect(process.env.REACT_APP_SOCKET_URI);
React.useEffect(()=>{
socket.on('finding', () => {
console.log('called');
});
},[socket])
const handleFindPartner = () => {
socket.emit('find', {
token: user.token,
});
};

Related

Child Component is not updating whenever i set the state in reactjs

Actually i want to update my child component
const callCoupon = async (promo) => {
/// result = some api call;
setpro(result);
}
const discountlist = pro.map(dis =>
<DiscountGrid
key = {dis.id}
id = {dis.id}
pname = {dis.pname}
prdPrice = {dis.prdPrice}
discountValue = {dis.discountValue}
/>);
when i click the button i used to call that function like this
const applyPromo = event => {
event.preventDefault();
callCoupon(promo);
}
the above one is worked to child component
and another way to set the state is
React.useEffect(() => {
preload();
}, []);
const preload = async () => {
const data = await some api call;
callCoupon(data.discountId);
}
when preload function call the coupon function the child component not updated , i think its slow may be
please help me to overcome these things
Thanks in advance
may be its useful for some others
i just update the dependency of use effect with state length.
React.useEffect(() => {
preload();
}, [cart.length]); // cart is state
Thank you

React with Socket.io first request in useEffect

I am using Socket.io in react app. I would like to do first request immediately after app loads so I put my first emit in useEffect hook:
useEffect(() => {
socket.emit("getCollectionsAndVolumes", socket.id);
}, []);
but it doesn't work. It does not do any request. So I realized that a problem is because maybe socket is not connected yet so I put little timeout like:
useEffect(() => {
const initialGetTimeout = setTimeout(() => {
clearTimeout(initialGetTimeout);
}, 1000);
})
and it works but it is only a workaround, how should I trigger first request right after app loads? Is there any other way to do that in React?
Add a parameter which gets changed when socket is connected as a dependency for useEffect React.useEffect(()=>{},[dependencies]);, if there is no such parameter, then try creating a state that maintains if socket is connected or not.
I fixed that by using:
socket.on("connect", () => {
socket.emit("getCollectionsAndVolumes", socket.id);
});
so when it is true it immediately executes request
Try to monitor socket with useEffect then add 'connect' listener. Inside that listener, create a callback function to emit first request.
// Websocket Listener
useEffect(() => {
const handleCode = (res) => {
setCode(res.code)
}
// Listeners
socket.on('res_mvp_code', handleCode)
// Join room
socket.on('connect', () => {
socket.emit('req_mvp_join', { room: 'mvp-1' }) // this is your first request
})
// Unlisteners
return () => {
socket.off('res_mvp_code', handleCode)
}
}, [socket])

Different WebSocket "onmessage" handlers depending on screen

My React Native app (iOS and Android) uses a single global WebSocket connection.
I want to respond to the same messages from the server differently depending on the screen, e.g. one way on the home screen (A) and differently on screen B.
Because the home screen is still mounted and "active" after screen B has been opened, presumably I can't just overwrite the websocket "onmessage" handler as that would lead to inconsistencies.
Can anyone point me in the right direction for what I'm trying to achieve?
Without seeing some code on what you're trying to achieve, I think what you want in general is a subscription model. You have only one handler registered with the socket, but that handler can delegate to other functions that can be added/removed as desired.
In general, I would recommend creating the websocket connection somewhere in the react tree such as a hook with a context provider. To avoid cluttering up the idea here, though, let's assume your websocket connection is defined a static context (i.e. in a module, rather than a component or hook).
// socket.js
const messageHandlers = new Set()
export const addMessageHandler = (handler) => {
messageHandlers.add(handler)
}
export const removeMessageHandler = (handler) => {
messageHandlers.delete(handler)
}
const socket = new WebSocket('ws://example.com')
socket.onmessage = (event) => {
messageHandlers.forEach((handler) => handler(event))
}
Then in your screen:
import { addMessageHandler, removeMessageHandler } from '/path/to/socket.js'
const SomeScreen = () => {
useCallback(() => {
const handler = (event) => { /* do something with event */ }
addMessageHandler(handler)
return () => removeMessageHandler(handler)
}, [])
}
This will keep the listener alive even if the screen is in the background. If you're using react-navigation, you can also make it register only while the screen is the current screen using useFocusEffect such as:
useFocusEffect(
useCallback(() => {
const handler = (event) => { /* do something with event */ }
addMessageHandler(handler)
return () => removeMessageHandler(handler)
}, [])
)

Socket event after API call not recording data correctly in React app

The App
I'm working on this App that loads data from the server and display it on a chart a display the total in a Gauge. The server also emits an event when a new data is store on the DB.
In the App.tsx I do an API call and also starts listening to this event.
The Problem
The API call works fine, all data is loaded and displayed, the problem is within the socketio event callback. The events array is empty and the currentTotal is zero, that is the initial values. I believe the problem has to do with a bad use of useState hook, but I don't really know how to do any other way.
I've tried to put the socket.on() in another useEffect or even out of one at the same scope of the useState hook, but it just created a lot of renderings and the page ended up taking some seconds to load.
This is my App.tsx file
...
const [events, setEvents] = useState<Event[]>([])
const [currentTotal, setCurrentTotal] = useState<number>(0)
useEffect(() => {
api
.get('/events')
.then((response) => {
const total = totals(response.data)
setCurrentTotal(total)
setEvents(response.data)
})
.catch()
socket.on('event-created', (newEvent: Event) => {
if (newEvent.rule_name === 'entering') {
newEvent.total = currentTotal + 1
} else {
newEvent.total = currentTotal - 1
}
setCurrentTotal(newEvent.total)
setEvents([...events, newEvent])
})
})
...
And this is my socket service in src/services/socketio.ts
import { io } from 'socket.io-client'
const socket = io('http://127.0.0.1:4001/')
export default socket
try to use the state callback function. This way you can get the current value:
App.tsx
setCurrentTotal((prev) => {
const val = newEvent.rule_name === 'entering' ? 1: -1;
prev = prev + val:
return prev;
})
setEvents((prev) => ([...prev, newEvent]))

React 16.8 hooks => how to properly inject an array of elements into the DOM

I'm using React's hooks (version 16.8.6) to inject an array of buttons with their respective index + value + onClick={}.
Basic code for brevity
function App() {
const [rooms, setRoomArray] = useState([]);
const roomArray = [
'room1',
'room2',
'room3',
'room4',
];
const handleNewMessage = (room) => {
setRoomArray(roomArray);
roomSpawner(rooms);
};
const test = () =>{
console.log('test here');
}
const roomSpawner = (rooms) =>{
return rooms.map((value,index) => (
<button onClick={test()} key={index}>{value}</button>
));
};
Everything works and displays properly, yet by console logging, I see that my console is going into a loop there is a loop and i'm trying to understand:
Is this is a loop or is it react's regular polling/reactive behavior?
Is this 'expensive' in terms of performance?
Would it make sense to insert into test() a socket connection polling a remote server?
Am I misusing the construct? If so please show me how to properly inject an array of elements.
Q1: Is this is a loop or is it react's regular polling/reactive behavior?
Answer: As commenters have pointed out, the loop is being caused by onClick={test()}. The onClick prop expects a function, and not a function call, to attach as an event handler. What it you can do is something like this:
const test = (index) =>{
console.log('I was called for room number: ' + index);
}
const roomSpawner = (rooms) =>{
return rooms.map((value,index) => (
<button onClick={() => test(index)} key={index}>{value}</button>
));
Q2: Is this 'expensive' in terms of performance?
Not at all. This is a perfect valid React implementation.
Q3: Would it make sense to insert into test() a socket connection polling a remote server?
I don't think so. You should insert your socket connection inside a useEffect() hook to be connected on mount. Something like this:
// INSIDE YOUR COMPONENT
useEffect(()=> {
// CONNECT TO SOCKET
// ON SOCKET MESSAGES, UPDATE SOME STATE WITH THE NEW ROOMS AND COMPONEN WILL RE-RENDER
return () => {
// DISCONNECT FROM SOCKET
}
},[]); // WITH THIS EMPTY ARRAY, THIS EFFECT WILL RUN ON 1ST RENDER, AND IT WILL DISCONECT FROM YOUR SOCKET WHEN IT GETS DISMOUNTED
React DOCS on useEffect()
Q4: Am I misusing the construct? If so please show me how to properly inject an array of elements.
I think you're fine. Keep the array of rooms in a state variable using useState() and use stateWithArrayOfRooms.map() to generate your components (buttons, or whatever). Something like.
// INSIDE YOUR COMPONENT
const [roomsArray, setRoomsArray] = useState([]);
useEffect(()=>{
// CONNECT TO YOUR SOCKET AND UPDATE roomsArray with new messages.
// setRoomsArray('newRoomsArray from socket');
return () => {
// DISCONNECT FROM SOCKET
}
},[]);
const roomItems = roomsArray.map((item, index) =>
<RoomComponent key={index or some other unique id} onClick={()=>test(index)}/>
);
return (
<RoomsContainer>
{roomItems}
</RoomsContainer>
);

Resources