socketio / socket.io-client

Realtime application framework (client)
https://socket.io
10.62k stars 3.04k forks source link

Cannot listen to event dispared in useEffect() Next js 14.0.4 #1601

Closed bbarchuk closed 9 months ago

bbarchuk commented 9 months ago

I'm encountering one issue where i can emit events from the client side (react) but not being capable of listen to events on the useEffect(). When i dispare an event everyone receives it in the network tab, so i don't know why i can't. It's worth to mention that im using rooms and when i change my server code to emit the event to everyone it triggers the useEffect, for example, intead of doing this: socket.to(room).emit("foo", bar) when i change to: io.emit("foo", bar) it works fine, but clarily i don't want that 'cause i'm trying to use rooms.

Socket.IO server version: 4.7.2

Server

io.on("connection", socket => {
  console.log("User connected")

  socket.on("join_room", room => {
    console.log(`User ${socket.id} joining room: ${room}`)
    socket.join(room)
  })

  socket.on("send_message", (data: { message: string, room: string }) => {
    const { message, room } = data

    console.log(room, message)
    socket.to(room).emit("message_from_room", message)
  })
})

Socket.IO client version Next js 14: 14.0.4

Client

const socket = io("http://localhost:8000")

type Message = string;

export default function RoomPage({ params }: { params: { roomId: string } }) {
  const [messages, setMessages] = useState<Message[]>([])

  useEffect(() => {
    socket.on("message_from_room", (message: string) => {
      setMessages((prev) => [...prev, message])
    }) 
  }, [])

  function handleSubmit(e: FormEvent<HTMLFormElement>) {
    e.preventDefault()

    const formElement = e.target as HTMLFormElement
    const formData = new FormData(formElement)

    const message = formData.get("message") as string
    socket.emit("send_message", { message: message, room: params.roomId })
  }

  return (
    <div className="max-w-4xl mx-auto h-screen flex items-end justify-center">
      <form onSubmit={handleSubmit} className="flex gap-5 items-center p-4">
        <input name="message" className="w-full px-3 py-1 bg-transparent outline-0 border border-gray-300 rounded-md text-black" placeholder="Write something..." />
          <button type="submit" className="px-4 py-1 bg-blue-500 rounded-md text-white">
            Send
          </button>
      </form>
      <div>
        { messages.map((message: Message, idx: number) => (
          <p key={idx}>{ message }</p>
        )) }
      </div>
    </div>
  )
}

With the above code i'm expecting to receive the message that anyone sent and update the messages state.

bbarchuk commented 9 months ago

I was able to resolve the problem by just doing 3 new things:

  1. Change the protocol i was using to connect to my socket.io server. Before io("http://localhost:8000") After io("ws://localhost:8000").
  2. I change the way my code works, instead of creating a new different socket connection in every component, i just created one and pass that to the components.
  3. And the last one and more important was in the useEffect, where before i was doing all together i moved the handler of the listener to a separate function and the pass to the .on() method.

The first point would be like this:

const socket: Socket = io("ws://localhost:8000")

In the second point i put that socket into a react ref (useRef hook), looking like this:

const sock = useRef<Socket>(socket)

And the last one and more important looks like this:

  useEffect(() => {
    const handleMessagesFromRoom = (message: string) => {
      setMessages((prev) => [...prev, message]);
    };

    sock.current.on("message_from_room", handleMessagesFromRoom);

    return () => {
      sock.current.off("message_from_room", handleMessagesFromRoom);
    };
  }, []);

I hope this is helpful for anyone with the same type of error. Thanks