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

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

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

How to integrate Phantom wallet in react project?

I actually desire to integrate phantom wallets into my custom hand website. Since I'm new to the web 3, I've just used Metamask and am unsure of what Solana and Phantom Wallets are.
Write a provider and then wrap your _app with this provider:
import {
ConnectionProvider,
WalletProvider,
} from '#solana/wallet-adapter-react'
import { WalletModalProvider } from '#solana/wallet-adapter-react-ui'
import { PhantomWalletAdapter } from '#solana/wallet-adapter-wallets'
import { useMemo } from 'react'
const WalletConnectionProvider = ({ children }) => {
const endpoint = useMemo(() => 'https://api.devnet.solana.com', [])
const wallets = useMemo(() => [new PhantomWalletAdapter()], [])
return (
<ConnectionProvider endpoint={endpoint}>
<WalletProvider wallets={wallets} autoConnect>
<WalletModalProvider>{children}</WalletModalProvider>
</WalletProvider>
</ConnectionProvider>
)
}
export default WalletConnectionProvider
or you manually check for window.solana the way you connect to window.ethereum
const isWalletConnected = async () => {
try {
const { solana } = window;
if (solana) {
if (solana.isPhantom) {
console.log("phantom wallet found");
// When using this flag, Phantom will only connect and emit a connect event if the application is trusted. Therefore, this can be safely called on page load for new users, as they won't be bothered by a pop-up window even if they have never connected to Phantom before.
// if user already connected, { onlyIfTrusted: true }
const response = await solana.connect({ onlyIfTrusted: false });
console.log(
"public key",
response.publicKey.toString()
);
setWalletAddress(response.publicKey.toString());
} else {
alert("Please install phantom wallet");
}
}
} catch (error) {
console.log(error);
}
};

Working with websocket in ReactJS project

I am tying to implement socket connection in ReactJs, I have read there documentation for how to implement more than one namespace in socket and this is how I did it after following there instructions
import {Manager} from "socket.io-client";
export const manager = new Manager(process.env.REACT_APP_SOCKET_API, {
transports: ["websocket"],
reconnection:true,
autoConnect:true,
});
// add the namespace [namespace1,namespace2]
export const chatSocket = manager.socket("/namespace1");
export const notesSocket = manager.socket("/namespace2");
manager.open((err) => {
if (err) {
console.log("socket connection error", err);
} else {
console.log("socket connection succeeded");
}
});
now I am trying to use it like that in my react app is this right or I should use it in another way because I don't see any result from my console.log mehtod
useEffect(() => {
chatSocket.on('connect',() => {
console.log('connected socket chat')
})
notesSocket.on('connect',() => {
console.log('connected socket notes')
})
},[])

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

Connecting socket io with react, not able to get data

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

Resources