Socket IO Same Connection runs twice
Unanswered
Saltwater Crocodile posted this in #help-forum
Saltwater CrocodileOP
Hello Guys, i currently wanted to create a Realtime Chat. For that i use socket.io-client on the frontend in NextJS and WebSockets in NestJS. When connecting to the socket it somehow connects twice. Here is the logic:
useSocket hook:
Now on Connection the socket.id gets logged twice in the consle and every request afterwards get executed twice. Does anyone know why that is?
useSocket hook:
import { BACKEND_URL } from '@/lib/constants';
import { useState, useEffect } from 'react';
import io from 'socket.io-client';
const useSocket = () => {
const [socket, setSocket] = useState<any>(null);
useEffect(() => {
const newSocket = io(BACKEND_URL);
setSocket(newSocket);
return () => {
newSocket.disconnect();
};
}, []); // Dependency on url to recreate socket on url change
return socket;
};
export default useSocket;const ChatContainer: FC<ChatContainerProps> = ({ projectId, sessionToken }) => {
const chatContainerRef = useRef(null);
const socket = useSocket();
const { data: session } = useSession();
const { data, isLoading } = useGetMessages(sessionToken, projectId);
const queryClient = useQueryClient();
const chats: Chat[] = data;
useEffect(() => {
if (socket) {
// Listen for incoming messages from the server
socket.on('onMessage', async (data: any) => {
await queryClient.cancelQueries(['getMessages']);
const prevList: Chat[] =
queryClient.getQueryData(['getMessages']) || [];
queryClient.setQueryData(['getMessages'], [...prevList, data.object]);
return { prevList };
});
}
}, [socket]);@WebSocketGateway({
cors: {
origin: 3000
},
})
export class EventsGateway implements OnModuleInit {
constructor(private readonly eventsService: EventsService) { }
@WebSocketServer()
server: Server
onModuleInit() {
this.server.on("connection", (socket) => {
console.log(socket.id)
})
}Now on Connection the socket.id gets logged twice in the consle and every request afterwards get executed twice. Does anyone know why that is?
27 Replies
Saltwater CrocodileOP
Please @ me on response
Toyger
@Saltwater Crocodile it connect twice probably because of StrictMode https://react.dev/reference/react/StrictMode#fixing-bugs-found-by-double-rendering-in-development, that run each function twice in dev environment, but you have
newSocket.disconnect(); in cleanup function so it should disconnect first one, and you should stay with single active connection.Saltwater CrocodileOP
Hello @Toyger
I already added StricMode:
const nextConfig = {
reactStrictMode: false
}
When console logging for example something in the useEffect it only appears once in the console. But there are two socket connections with the same id and every operation i do afterwards is executed twice. SO for example when i send a text it is created two time in the database
I already added StricMode:
const nextConfig = {
reactStrictMode: false
}
When console logging for example something in the useEffect it only appears once in the console. But there are two socket connections with the same id and every operation i do afterwards is executed twice. SO for example when i send a text it is created two time in the database
Before i addedd StrictMode: false there would be two useEffect console logs. Now there is only one but still two connections.
Saltwater CrocodileOP
Could it be that the problem doent come from double rendering or mounting but rather something from NestJS WebsocketsGateway?
Toyger
hard to say without all code, you need to check all your listeners that you make, how much time they executes, for example you have
socket.on('onMessage', but it's not clear will it move this listener to another socket, maybe you need disable them with .off or .removeAllListeners in cleanup functionSaltwater CrocodileOP
import { MessageBody, SubscribeMessage, WebSocketGateway, WebSocketServer } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { PrismaService } from 'src/prisma.service';
import { EventsService } from './events.service';
import { OnModuleInit } from '@nestjs/common';
import { CreateChatDto } from './dto/events.dto';
@WebSocketGateway({
cors: {
origin: 3000
},
})
export class EventsGateway implements OnModuleInit {
constructor(private readonly eventsService: EventsService) { }
@WebSocketServer()
server: Server
onModuleInit() {
this.server.on("connection", (socket) => {
console.log(socket.id)
})
}
@SubscribeMessage('newMessage')
async onNewMessage(@MessageBody() body: any) {
const newMessage = await this.eventsService.newMessage(body)
this.server.emit("onMessage", {
sender: body.authorId,
content: body.content,
object: newMessage
})
}
}import { BadRequestException, Injectable } from '@nestjs/common';
import { Socket } from 'socket.io';
import { PrismaService } from 'src/prisma.service';
import { CreateChatDto } from './dto/events.dto';
@Injectable()
export class EventsService {
constructor(private readonly prismaService: PrismaService) { }
async newMessage(dto: CreateChatDto) {
try {
const user_Project = await this.prismaService.user_Project.findFirst({
where: {
projectId: dto.projectId,
userId: dto.authorId
}
})
const message = await this.prismaService.chat.create({
data: {
content: dto.content,
userId: user_Project.id,
projectId: dto.projectId
}, include: {
user: {
include: {
user: true
}
}
}
})
if (!message) throw new BadRequestException("Something went wrong")
return message
} catch (error) {
throw new BadRequestException("Something went wrong")
}
}
}Thats the only one i have
And everytime newMessage is called there are 2 db entries created
Toyger
I don't see code that emits this
newMessage eventSaltwater CrocodileOP
const sendMessage = () => {
if (!socket) return;
if (message.trim() !== '' && session) {
// Check if session is available
socket.emit('newMessage', {
content: message,
projectId: projectId,
authorId: session.user.id,
});
setMessage('');
}
};Toyger
more likely you need to add cleanup function here
as I said with either
or removeAllListeners https://socket.io/docs/v4/listening-to-events/#socketremovealllistenerseventname
useEffect(() => {
if (socket) {
// Listen for incoming messages from the server
socket.on('onMessage', async (data: any) => {
await queryClient.cancelQueries(['getMessages']);
const prevList: Chat[] =
queryClient.getQueryData(['getMessages']) || [];
queryClient.setQueryData(['getMessages'], [...prevList, data.object]);
return { prevList };
});
}
}, [socket]);as I said with either
.off() https://socket.io/docs/v4/listening-to-events/#socketoffeventname-listener or removeAllListeners https://socket.io/docs/v4/listening-to-events/#socketremovealllistenerseventname
Saltwater CrocodileOP
Doent work like this, that way the new chat isnt shown in realtime and there are still two entries
I still dont understand why the socket.id on Connection is logged twice, i feel like the problem comes from there. But idk
@Saltwater Crocodile Doent work like this, that way the new chat isnt shown in realtime and there are still two entries
Toyger
it doesn't make sense, you have single socket, so it will get all callbacks while exist.
you need to check where you doubling happens first, add logs to all events emmiters and handlers and log time also, to understand what happens after what.
you need to check where you doubling happens first, add logs to all events emmiters and handlers and log time also, to understand what happens after what.
Saltwater CrocodileOP
CONNECTING:
CONNECT TIME: 2024-03-28T10:43:22.472Z
NVzlC7KwB_HtON6jAAAB
CONNECT TIME: 2024-03-28T10:43:22.473Z
NVzlC7KwB_HtON6jAAAB
"2024-03-28T10:43:28.476Z"
Only logged once.
NEW MESSAGE TIME: 2024-03-28T10:43:28.479Z
NEW MESSAGE TIME: 2024-03-28T10:43:28.479Z
onModuleInit() {
this.server.on("connection", (socket) => {
console.log("CONNECT TIME: ", new Date())
console.log(socket.id)
})
}CONNECT TIME: 2024-03-28T10:43:22.472Z
NVzlC7KwB_HtON6jAAAB
CONNECT TIME: 2024-03-28T10:43:22.473Z
NVzlC7KwB_HtON6jAAAB
const sendMessage = () => {
console.log('SEND');
if (!socket) return;
if (message.trim() !== '' && session) {
console.log('EMIT TIME: ', new Date());
// Check if session is available
socket.emit('newMessage', {
content: message,
projectId: projectId,
authorId: session.user.id,
});
setMessage('');
}
};"2024-03-28T10:43:28.476Z"
Only logged once.
@SubscribeMessage('newMessage')
async onNewMessage(@MessageBody() body: any) {
console.log("NEW MESSAGE TIME: ", new Date())
const newMessage = await this.eventsService.newMessage(body)
this.server.emit("onMessage", {
sender: body.authorId,
content: body.content,
object: newMessage
})
}NEW MESSAGE TIME: 2024-03-28T10:43:28.479Z
NEW MESSAGE TIME: 2024-03-28T10:43:28.479Z
It doesnt make sense at all
So there is a time difference between both connection of like 1 ms
But they are still getting the same id
Toyger
one of problems you are using socket with custom hook, but socketio suggest use it as single instance https://socket.io/how-to/use-with-react
maybe your cleanup in hook is wrong
and maybe here you need use
but easier will be if instead hook you'll use single instance.
maybe your cleanup in hook is wrong
return () => {
newSocket.disconnect();
};and maybe here you need use
socket.but easier will be if instead hook you'll use single instance.
Saltwater CrocodileOP
import { io } from "socket.io-client";
import { BACKEND_URL } from "./lib/constants";
export const socket = io(BACKEND_URL)Changed it now to this and importing this into the components. But ti still gets two sessions
@Saltwater Crocodile ts
import { io } from "socket.io-client";
import { BACKEND_URL } from "./lib/constants";
export const socket = io(BACKEND_URL)
Changed it now to this and importing this into the components. But ti still gets two sessions
Toyger
and it will be also multiple one, you need single socket in app.js as they show in example
Saltwater CrocodileOP
Okay, i jsut found out, it has nothing to do with next or any frontend thing. Since when connecting to the socket with postman it also logs the id twice.
Saltwater CrocodileOP
When starting up NestJS server the EventsGateway gets logged twice
Saltwater CrocodileOP
Okay i fixed it, the Problem was in my app.module.ts i had the EventsModule and the Gateway as imports and providers. Therefore it listened twice
Thanks for your help