I m trying out socket io for my project to show online friends, and I was wondering (actually, it looks kinda strange to me) that whenever I try to rerender the page (it doesn t matter if a user changes his profile info or send a friend req) the useEffect which is in charge of initializing the socket will disconnect and reconnect. Well... I agree that if the page is being refreshed or closed, the socket should be closed, but not whenever a user sends a friend request or changes his profile info... I just started to include the socket and the functionality is not full but so far at the beginning, having these various reconnections and creation of Sockets is strange. plusssss, my main concern shows itself on rerender; the socket will be disconnected (which is normal) and won t connect back (which is abnormal), but i only want this behavior on actual refreshes or logouts :( again, I will try to be as clear as possible, I have my case open on my screen for further explanation on your questions asap :)
File (Client): socket.js:
import { io } from socket.io-client ;
export const initializeSocket = (token, userInfoQuery, setOnlineFriends, setSocket) => {
if (token && userInfoQuery) {
const socket = io(process.env.REACT_APP_API_URL, { auth: { token } });
const friendIds = userInfoQuery.friends
socket.emit("userFriends", friendIds);
socket.on("onlineUsers", (users) => {
setOnlineFriends(users);
});
socket.on("addFriend", (friend) => {
console.log("Received friend request:", friend);
});
socket.on("removeFriend", (friend) => {
console.log("Received friend request:", friend);
});
setSocket(socket);
return socket;
}
return null;
};
File (Client): App.js:
export default function App() {
const { token, logout, login } = useContext(AuthContext);
const [onlineFriends, setOnlineFriends] = useState([]);
const [socket, setSocket] = useState(null);
const userInfoQuery = useQuery("userInfo", async () => {
const response = await axios.get(
`${process.env.REACT_APP_API_URL}/api/user`,
{
headers: {
Authorization: "Bearer " + token,
},
}
);
return response.data;
});
const userBooksQuery = useQuery("userBooks", async () => {
const response = await axios.get(
`${process.env.REACT_APP_API_URL}/api/user/books`,
{
headers: {
Authorization: "Bearer " + token,
},
}
);
return response.data;
});
useEffect(() => {
if (socket) {
socket.connect();
}
if (token && userInfoQuery.data && !socket) {
initializeSocket(token, userInfoQuery.data, setOnlineFriends, setSocket);
}
return () => {
if (socket) {
socket.disconnect();
}
};
}, [token, userInfoQuery.data, socket]);
return ( ...
)
as you can see, I initaiated the socket and all its functionality on one useEffect and those dep array. not having the userInfoQuery as dep would not rerender the page since online friends are coming from the socket.
File (Server): socketController.js:
const knexConfig = require("../knexfile");
const { knex } = require("knex");
const db = knex(knexConfig);
const io = require("socket.io");
const jwt = require("jsonwebtoken");
require("dotenv").config();
module.exports.socketController = (io) => {
const onlineFriends = new Map();
io.on("connection", async (socket) => {
console.log(`A user connected ${socket.id}`);
try {
const token = socket.handshake.auth.token;
const decoded = jwt.verify(token, process.env.JWT_SIGN_KEY);
const userId = decoded.user_id;
await db("user").where("user_id", userId).update({ is_online: 1 });
const user = await db("user").where("user_id", userId).first();
onlineFriends.set(userId, user);
socket.join(userId);
const onlineFriendList = Array.from(onlineFriends.values());
socket.emit("onlineUsers", onlineFriendList);
socket.on("userFriends", async (friendsList) => {
friendsList.forEach((friend) => {
socket.join(friend.friend);
});
const onlineUsers = await db("user")
.whereIn(
"user_id",
friendsList.map((friend) => friend.friend)
)
.andWhere("is_online", 1);
onlineUsers.forEach((user) => onlineFriends.set(user.user_id, user));
const updatedOnlineFriendList = Array.from(onlineFriends.values());
socket.broadcast.emit("onlineUsers", updatedOnlineFriendList);
});
socket.on("addFriend", async (friendId) => {
const recipientUser = onlineFriends.get(friendId);
if (recipientUser) {
const recipientSocket = io.sockets.sockets.get(recipientUser.socket_id);
if (recipientSocket) {
recipientSocket.emit("addFriend", userId);
}
}
});
socket.on("removeFriend", async (friendId) => {
const recipientUser = onlineFriends.get(friendId);
if (recipientUser) {
const recipientSocket = io.sockets.sockets.get(recipientUser.socket_id);
if (recipientSocket) {
recipientSocket.emit("removeFriend", userId);
}
}
});
socket.on("disconnect", async () => {
await db("user").where("user_id", userId).update({ is_online: 0 });
onlineFriends.delete(userId);
const updatedOnlineFriendList = Array.from(onlineFriends.values());
socket.broadcast.emit("onlineUsers", updatedOnlineFriendList);
console.log(`${socket.id} disconnected`);
});
} catch (error) {
console.error("Authentication error:", error.message);
socket.disconnect();
}
});
};
I feel the server-side is pretty much fine in terms of sending back the online friend users. again if the server-side had some problem or you had suggestions on it, feel free to let me know...
each user object if like this :
{
avatar_image: "https://i.pravatar.cc/150?img=13"
email: "ross001@gmail.com"
favorite_genre: "comedy"
first_name: "ross"
friends: Array [ {…} ]
goal_set: 9
last_name: "test"
username: "rosss001"
}
thanks for coming this far... this problem stoped me to move on my project for days, would appreciate if you could help me out here :)
should you have any suggestion, let me know
again, I have my case open for answering questions
Although not being efficient in useEffect, I tried to do socket.connect()
to attempt to reconnect to the socket, but it seems not to work. Also, when a user changes his profile info, I tried to in my component refetch the data to trigger the useEffect by : queryClient.refetchQueries("userInfo");
. again it seems it rerenders and disconnects the whole socket and causes the problem altogether. on the other hand, I need to rerender the page to inform the user and the changes made to his account! the socket.connect()
at the beginning of useEffect was my ultimate try...