Connecting socket io with react, not able to get data - reactjs

I'm attempting to connect a component from React to get some data from the server with live updates via socket.io
In my express server I have the following:
(partial but the relevant part)
const apiPort = 3000
const server = app.listen(apiPort, () => console.log(`Server running on port ${apiPort}`))
io.on('connection', (client) => {
client.on('subscribeToTimer', (interval) => {
console.log('client is subscribing to timer with interval', interval);
setInterval(() => {
client.emit('timer', new Date());
}, interval);
});
});
io.listen(server);
console.log('listening on port ', apiPort);
I then have a helper api.js file on the client side:
import openSocket from 'socket.io-client';
const socket = openSocket('http://localhost:3000');
function subscribeToTimer(cb) {
socket.on('timer', timestamp => cb(null, timestamp));
socket.emit('subscribeToTimer', 1000);
}
export { subscribeToTimer }
I then import this method into my react component:
import { subscribeToTimer } from './api';
I then create a state and attempt to update it:
const [timestamp, setTimeStamp] = useState('no timestamp yet')
useEffect(() => {
subscribeToTimer((err, tstamp) => setTimeStamp);
console.log(timestamp);
});
On the server side I get the console log:
client is subscribing to timer with interval 1000
On the client side it console.logs:
'no timestamp yet'
I'm not getting the update from the socket. Any idea what I'm doing wrong? I'm following a tutorial and incorporating it into my react project.

Pass the tstamp to the setTimeStamp function, you are just passing the reference of the function.
Try this.
const [timestamp, setTimeStamp] = useState('no timestamp yet')
useEffect(() => {
subscribeToTimer((err, tstamp) => setTimeStamp(tstamp));
console.log(timestamp);
});

I believe your problem is here:
useEffect(() => {
subscribeToTimer((err, tstamp) => setTimeStamp(tstamp));
console.log(timestamp);
}
You are not assign8ng anything to your state

Related

Connection leak with a Socket.IO component in React

I have a React application that uses Socket.IO. The Socket instance is in a React component.
I have been noticing that the action of logging out and logging into my application, which should unmount the component and close the connection and then remount and reopen the connection leads to a socket leak/creation of duplicate socket connection. I have also managed to get the application into a state where it quickly spews off new connections leading to starvation, but have not been able to replicate. This hit production once.
Here is the client code:
const Socket = React.memo(() => {
const [isLoadingSocket, setIsLoadingSocket] = useState<boolean>(false)
const socketRef = useRef<SocketIO<ServerToClientEvents, ClientToServerEvents> | null>(null)
const socketNeedsRestart = isFocused ? !isLoadingSocket : false
async function initializeSocket() {
const token = await getToken()
setIsLoadingSocket(true)
if (socketRef.current) {
socketRef.current.disconnect()
}
socketRef.current = io(`${SOCKET_HOST}`, {
secure: true,
reconnectionDelay: 5000,
transports: ['websocket', 'polling'],
path: ENVIRONMENT !== Environment.local ? '/api/socket.io/' : '',
auth: {
token,
},
})
console.log(`socket initialized`)
}
useEffect(() => {
if (socketNeedsRestart) {
initializeSocket()
}
}, [socketNeedsRestart]) //eslint-disable-line
useEffect(() => {
if (socketRef.current) {
socketRef.current.on(SocketLifecycleEvent.Connect, () => {
console.log('socket connected')
setIsLoadingSocket(false)
})
socketRef.current.on(SocketMessage.UsersOnline, (message) => {
updateOnlineUsers(message.onlineUserIDs)
})
}
return () => {
if (socketRef.current) {
socketRef.current.off(SocketLifecycleEvent.Connect)
socketRef.current.off(SocketLifecycleEvent.ConnectionError)
socketRef.current.off(SocketLifecycleEvent.Disconnect)
}
}
}, [isLoadingSocket])
useEffect(() => {
socketRef.current?.disconnect()
}, [])
return <></>
})
export default Socket
The component is used once in the page that a user gets to after login. I can provide server code but it doesn't do anything except notify all users every time someone connects. What's causing the connection leak? How can I re-create the rapid-fire leak?
Is your last useEffect doing what you expect it to do? It looks like this is supposed to be the cleanup on unmount but you are not returning a function there.
Did you try something like this?:
useEffect( () => () => socketRef.current?.disconnect(), [] );

React/Socket.io chat app not working on heroku

I have a chat app I made using React for the frontend, DRF for the backend and I also have a node.js server within the React app for socket.io The issue is that the chat doesn't work basically (it works fine locally). When a message is sent it's not emitted and only shows up when I refresh since it's then pulled from the DB instead. I have gone through many threads on here for this issue but can't figure out what I'm doing wrong.
My server:
const server = require("http").createServer();
const io = require("socket.io")(server, {
cors: {
origin: "*",
},
});
const PORT = process.env.PORT || 5000;
const NEW_CHAT_MESSAGE_EVENT = "newChatMessage";
io.on("connection", (socket) => {
console.log('Client connected')
// Join a conversation.
const {roomId} = socket.handshake.query;
socket.join(roomId);
// Listen for new messages
socket.on(NEW_CHAT_MESSAGE_EVENT, (data) => {
io.in(roomId).emit(NEW_CHAT_MESSAGE_EVENT, data);
});
// Leave the room if the user closes the socket
socket.on("disconnect", () => {
socket.leave(roomId);
});
});
server.listen(PORT, (error) => {
if (error) throw error;
console.log(`Listening on port ${PORT}`);
});
Hook I made for the frontend:
const NEW_CHAT_MESSAGE_EVENT = "newChatMessage"; // Name of the event
const SOCKET_SERVER_URL = `https://<my-react-frontend>.herokuapp.com`;
export const useChat = () => {
const socketRef = useRef();
const {messages, setMessages, activeConvo, headerConvo, reloadSideBar, setReloadSideBar} = useActiveConvo()
const roomId = activeConvo
useEffect(() => {
console.log('useChat useEffect ran')
// Creates a WebSocket connection
socketRef.current = socketIOClient(SOCKET_SERVER_URL, {
query: {roomId},
});
// Listens for incoming messages
socketRef.current.on(NEW_CHAT_MESSAGE_EVENT, (message) => {
const incomingMessage = {
message: message.body,
created_by: localStorage.getItem('currentUserID'),
};
console.log('messages set in useChat useFfect')
setMessages((messages) => [...messages, incomingMessage]);
});
// Destroys the socket reference
// when the connection is closed
return () => {
socketRef.current.disconnect();
};
}, [roomId]);
// Sends a message to the server that
// forwards it to all users in the same room
const sendMessage = (messageBody) => {
socketRef.current.emit(NEW_CHAT_MESSAGE_EVENT, {
body: messageBody,
senderId: socketRef.current.id,
});
const fetchContents = {
message: messageBody,
created_by: localStorage.getItem('currentUserID'),
convo_id: activeConvo ? activeConvo : headerConvo
}
fetch(`https://<my-drf-backend>.herokuapp.com/api/save-message/`, authRequestOptions(('POST'), fetchContents))
.then(response => response.json())
.then(setReloadSideBar(reloadSideBar + 1))
.catch(error => console.log(error))
};
return {messages, sendMessage};
};
The issue in most of the threads appeared to be either still using the localhost url on the frontend or not using process.env.PORT in the server but it's still busted after fixing that. I also saw someone mention in another thread that the folder structure was the issue so I tried having the server file in the root of the react app and having it in it's own folder under "src", no dice.
In case anyone faces this same issue, I solved it by putting the server in a separate app on heroku

Socket.io and Next.Js

I am working on a project for an interview and have been asked to create a NextJS app using Socket.io for realtime chat. I have the chat functionality working, but one of my requirements is to have an area where a user can see a list of current users. While I've found examples for Express servers, I cannot seem to work out how to do this using Next's API system. I have two connected issues:
Maintaining a list of users with chosen display names (not just the socket id)
Accessing and returning a current user list whenever a user joins or leaves.
I haven't had any luck scanning the docs.
Here is the server function:
import { NextApiRequest } from 'next';
import { NextApiResponseServerIO } from '../../types/next';
import { Server as ServerIO } from 'socket.io';
import { Server as NetServer } from 'http';
export const config = {
api: {
bodyParser: false
}
}
export default async (req: NextApiRequest, res: NextApiResponseServerIO) => {
if (!res.socket.server.io) {
console.log("** New Socket.io server **")
// adapts the Next net server to http server
const httpServer: NetServer = res.socket.server as any;
const io = new ServerIO(httpServer, {
path: '/api/socketio'
})
io.on('connect', async (socket) => {
socket.join('main')
// where I plan to put the code to send a current list
})
io.on('disconnect', socket => {
socket.leave('main')
})
res.socket.server.io = io;
}
res.end();
}
And the related client code:
useEffect((): any => {
const url = process.env.NEXT_BASE_URL as string | "";
// connect to socket server
const socket = io(url, {
path: "/api/socketio",
});
// log socket connection
socket.on("connect", () => {
dispatch(connect(socket.id));
dispatch(updateId(socket.id))
});
//updates chat on message dispatch
socket.on("message", (message: IMsg) => {
dispatch(receive(message));
});
socket.on('updateUsersList', (users) => {
console.log("Is this the users", users)
})
//server disconnect on unmount
if (socket) return () => dispatch(disconnect(socket));
}, []);

React - state variable not reflecting changes

I am working on adding websockets to my Node/React app to automatically reflect changes to all the clients. So I have a websockets helper module that has onclose, onopen and onmessage events as well as a readyState function. So my component that needs the updated websocket values makes a call to that module and gets back data. That data variable is coming over empty, but when I console it out in the onmessage event in the module itself, it has all the info I want.
So here is how I call the websocket module in my component:
const onConnected = (socket) => {
socket.send(
JSON.stringify({
eventType: 'clientCount'
})
);
};
const { socket, readyState, reconnecting, data } = useWebsocket({
url: wsURL + ':' + process.env.REACT_APP_WS_PORT,
onConnected
});
I have a useEffect that should spit out the updated values from data:
useEffect(() => {
console.log('data changed!!!!');
console.log({ data });
console.log({ socket });
console.log({ readyState });
if (data) {
setNumberClients(data.numberClients);
setNumberIpads(data.numberIpads);
}
}, [data, readyState]);
And finally here is my websockets module itself:
import { useState, useEffect, useRef } from 'react';
export default function useWebsocket({ url, onConnected }) {
const [data, setData] = useState([]);
const [reconnecting, setReconnecting] = useState(false);
const socket = useRef(null);
useEffect(() => {
console.log('running socket hook');
socket.current = new WebSocket(url);
socket.current.onopen = () => {
console.log('connected');
onConnected(socket.current);
};
socket.current.onclose = () => {
console.log('closed');
if (socket.current) {
if (reconnecting) return;
setReconnecting(true);
setTimeout(() => setReconnecting(false), 2000);
socket.current.close();
socket.current = undefined;
}
};
socket.current.onmessage = (e) => {
const wsData = JSON.parse(e.data);
console.log('message received ', wsData);
//setData((prev) => [...prev, wsData]);
setData(wsData);
};
return () => {
socket.current.close();
socket.current = null;
};
}, [reconnecting, url]);
const readyState = () => {
if (socket.current) {
switch (socket.current.readyState) {
case 0:
return 'CONNECTING';
case 1:
return 'OPEN';
case 2:
return 'CLOSING';
case 3:
return 'CLOSED';
default:
return;
}
} else {
return null;
}
};
return {
socket: socket.current,
readyState: readyState(),
reconnecting,
data
};
}
So data is always an empty array when I console it out in my component. But in the websockets module, it(wsData) has the info I need.
One More Thing: I am following the tutorial here: https://github.com/devmentorlive/websocket-direct-chat-client/tree/2-as-a-hook/src/chat
Update 2: I have a github repo showing the exact issue here: https://github.com/dmikester1/websockets-test
Use Server and Start scripts to kick things off.
It's not a problem on front-end side.
I think you have used useWebsocket hook twice - once in the SchedulePage and again in the ClientCountContainer so that you can check if 2 clients are displayed.
The problem is that the socket client you defined in ClientCountContainer component is not receiving the message from the server.
After looking at the websocket server, I noticed that it broadcasts messages to websocket clients that are saved in clients array. Not all the websocket client is saved in this array, but only the client which sends {eventType: 'connect'} message to the server is saved in that array.
The websocket client you created using useWebsocket hook in SchedulePage component is saved in clients array on websocket server, because it first sends {eventType: 'connect'} message to the server. But the websocket client you created in ClientCountContainer is not saved in that array.
Therefore, the messages containing clientCount information is not sent to the second websocket client which is defined in ClientCountContainer.
To get rid of this from happening, you can simply add this code snippet in the ClientCountContainer.js, which sends {eventType: 'connect} message to the server so that this client can be added to broadcast array.
.....
const onConnected = (socket) => {
// Add these lines
socket.send(
JSON.stringify({
eventType: 'connect'
})
);
// This is the original line
socket.send(
JSON.stringify({
eventType: 'clientCount'
})
);
};
......
Please let me know if you have further issues.
Thanks.
I think that the useEffect would have to run all the time, not only when ur url changed or when it is reconnecting.
Try giving this a try: https://stackoverflow.com/a/60161181/10691892

How to avoid reconnections with socket.io-client and React app?

I tried to connect React client to my Socket.IO server. I noticed Socket.IO client reconnects every +/- 5s. When I try do the same thing with vanilla html/js simple app everything works crrectly.
Inside React component:
useEffect(() => {
const s = getChatClient();
}, []);
Inside socket.io-client module:
var chatClient;
export function getChatClient(
token = "secret"
) {
if (!chatClient) {
chatClient = io("http://localhost:5000/chat", {
query: {
token,
},
});
chatClient
.on("connect", () => {
chatClient.emit("textMessage", "123cos");
})
.on("hello", (msg) => {
console.log("12");
});
}
return chatClient;
}
BTW: I know I can do it export const etc (I've changed to this version becouse I thought it'll help).
I tried different ways to resolve this problem, but I got in stuck. Any ideas?
Log from the server when I open html/js simple client:
15:30:00 User Ilona connected to the socket.io server on /
and when I quit:
15:29:12 User Ilona disconnected
With React App:
15:30:00 User Ilona connected to the socket.io server on '/'
15:30:05 User Ilona disconnected
15:30:10 User Ilona connected to the socket.io server on '/'
15:30:15 User Ilona disconnected
15:30:20 User Ilona connected to the socket.io server on '/'
15:30:25 User Ilona disconnected
The problem isn't related with component rerender or something like this.
I'm working on MacOS Big Sur.
Consider creating, and then consuming from a context:
SocketContext.jsx :
import { createContext, useState } from 'react';
export const SocketContext = createContext();
export default function SocketContextProvider(props) {
const [sock, setSocket] = useState(null);
let socket = async () => {
if (sock) {
return Promise.resolve(sock); // If already exists resolve
}
return new Promise((resolve, reject) => {
let newSock = io('URL'),
{
query: {
// Options
},
}; // Try to connect
let didntConnectTimeout = setTimeout(() => {
reject();
}, 15000) // Reject if didn't connect within 15 seconds
newSock.once('connect', () => {
clearTimeout(didntConnectTimeout); // It's connected so we don't need to keep waiting 15 seconds
setSocket(newSock); // Set the socket
resolve(newSock); // Return the socket
})
});
};
return (
<SocketContext.Provider value={{ socket }}>
{props.children}
</SocketContext.Provider>
);
}
Component.jsx :
import { useContext, useState, useEffect } from 'react';
import { SocketContext } from './SocketContext.jsx';
export default function MyComponent() {
const { socket } = useContext(SocketContext);
const [sock, setSock] = useState(null);
useEffect(() => {
socket()
.then((resultSocket) => setSock(resultSocket))
.catch(() => {
/* Catch any errors here */
console.log('Couldn\'t connect the socket!')
});
}, []);
return (
<div>
<code>I'm a context consumer...</code>
</div>
);
}

Resources