why does my socket goes into a infinite loop?

Frontend:

useEffect(() => {
    socketRef.current = io.connect("...", { transports : ['websocket'] })
    socketRef.current.emit("online", id)

    socketRef.current.on("message", ({ name, message }) => {
        setChat([ ...chat, { name, message , system: false } ])
    })

    socketRef.current.on("online", ( userID ) => {
        setChat([ ...chat, { name: userID , message: `has logged on` , system: true } ])
    })

    socketRef.current.on("offline", ( userID ) => {
        setChat([ ...chat, { name: userID , message: `${userID} has logged off` , system: true } ])
    })

    return () => {
        socketRef.current.disconnect()
    }
},[ chat ])

Backend:

io.on('connection', socket => {
  socket.on('message', ({ name, message , userID }) => {
    io.emit('message', { name, message , userID })
  })

  socket.on('online', ( userID ) => {
    onlineApprovers.push({ user: userID , id: socket.id })

    console.log(userID + ' is online')
    io.emit('online', userID)
  })

  socket.on('disconnect', () => {
    console.log(onlineApprovers.find(a => a.id === socket.id).user + ' is offline')
    io.emit('offline', onlineApprovers.find(a => a.id === socket.id).user)
  })
})

a little bit of context of what my code does: when a user goes onto my site, it gets their user id, emits the online socket event, and then the backend logs userid is online, and when they leave my site, the socket will disconnect and then it will log userid is offline.

Can anyone tell me why does this goes into an infinite loop? When I log on it keeps logging 123 is online and then it logs 123 is offline and then 123 is online and so on. It logged that when I didn’t even close my window.

/ edit /

after removing these from my code:

// socketRef.current.on("online", ( userID ) => {
//  setChat([ ...chat, { name: userID , message: `has logged on` , system: true } ])
// })

// socketRef.current.on("offline", ( userID ) => {
//  setChat([ ...chat, { name: userID , message: `${userID} has logged off` , system: true } ])
// })       

it does not end up in an infinite loop, but then my frontend can’t receive the data from my backend.

Answer

When a user joins/the chat state changes, you’re emitting an online event to the server, which the server then responds to by emitting a online event to all connected clients, which causes the following listener to execute:

socketRef.current.on("online", ( userID ) => {
  setChat([ ...chat, { name: userID , message: `has logged on` , system: true } ])
})

as you’re calling setChat() here and passing through a new array, this causes your react-component to re-render, which makes the use-effect function execute again. This then fires the online event again which causes the loop.

Instead, you can create your socket connection when your component mounts by passing [] as a dependency to your useEffect(), and use your state updater function of setChat() to access the most up-to-date value of chat:

useEffect(() => {
  socketRef.current = io.connect("...", { transports : ['websocket'] });
  socketRef.current.emit("online", id);

  socketRef.current.on("message", ({ name, message }) => {
    setChat(chat => [ ...chat, { name, message , system: false } ]);
  });

  socketRef.current.on("online", ( userID ) => {
    setChat(chat => [ ...chat, { name: userID , message: `has logged on` , system: true } ]);
  });

  socketRef.current.on("offline", ( userID ) => {
    setChat(chat => [ ...chat, { name: userID , message: `${userID} has logged off` , system: true } ]);
  });

  return () => {
    socketRef.current.disconnect();
  };

}, []);