Manejo de salas con SocketIO y React Hooks

React
React Native
Node

Esto crea una aplicación de chat de demostración que involucra salas de socket.io integradas mediante enlaces React. El cliente puede conectarse y alternar entre salas de chat, donde las actualizaciones serán replicadas por eventos socket.io. Dependiendo de la sala seleccionada, transmitirá mensajes de chat a los clientes de socket que usen esa sala.

(Una vez que se ejecuta, podemos activar varias pestañas del navegador y ver si los mensajes de chat se actualizan a través de sockets).

 

Configurar el servidor SocketIO usando Nodejs

Creamos un servidor de nodo con una configuración simple de socket.io

var http = require('http').createServer().listen(3000);
var io = require('socket.io').listen(http);
io.on('connection', (socket) => {
   console.log(`Connected: ${socket.id}`);
   socket.on('disconnect', () =>
      console.log(`Disconnected: ${socket.id}`));
   socket.on('join', (room) => {
      console.log(`Socket ${socket.id} joining ${room}`);
      socket.join(room);
   });
   socket.on('chat', (data) => {
      const { message, room } = data;
      console.log(`msg: ${message}, room: ${room}`);
      io.to(room).emit('chat', message);
   });
});

Aquí es necesario instalar el paquete npm socket.io y el servidor de socket se crea en el mismo puerto 3000 que con el servidor http.

  • io.on('connection') se refiere a la inicialización de cualquier conexión de socket de cliente y, luego, sé (socket) => hace referencia dentro de ese socket en particular con una identificación única.
  • Cada socket puede unirse a la sala socket.io requerida emitiendo el nombre de la sala al join evento, y el servidor escucharía el join evento y conectaría ese socket a la habitación determinada.
  • Cuando socket emite un mensaje de chat a un chat evento, se transmitirá a todos los sockets de clientes en esa sala en particular, creando estas salas de chat.

 

Configurar cliente frontend usando Reactjs Hooks

Instalamos el socket.io-client paquete y creemos funciones relacionadas con el socket para tratar con el servidor de socket y eso se usará con los hooks de React.

import io from 'socket.io-client';
let socket;
export const initiateSocket = (room) => {
  socket = io('http://localhost:3000');
  console.log(`Connecting socket...`);
  if (socket && room) socket.emit('join', room);
}
export const disconnectSocket = () => {
  console.log('Disconnecting socket...');
  if(socket) socket.disconnect();
}
export const subscribeToChat = (cb) => {
  if (!socket) return(true);
  socket.on('chat', msg => {
    console.log('Websocket event received!');
    return cb(null, msg);
  });
}
export const sendMessage = (room, message) => {
  if (socket) socket.emit('chat', { message, room });
}
  • Aquí socket se declara como una variable y no se inicia al inicio, porque eso hará la conexión del socket tan pronto como se cargue el archivo. Pero aquí se inicia dentro de la función para que el cliente pueda controlar cuándo realizar la conexión del socket.
  • Tan pronto como se conecte io(URL...) , emitirá un evento para unirse a una habitación en particular. Tenga en cuenta que el concepto de sala solo se mantiene en el backend, y el socket del cliente en el frontend no conoce las salas.
  • El evento de suscripción usa una función de devolución de llamada para interactuar con su propósito, donde se llamará a la función de devolución de llamada data para cualquier mensaje en el evento dado.

 

Estas funciones de socket se pueden usar en la aplicación React fácilmente con ganchos. Con useState hooks mantendrá sus variables para room (seleccionado), message (mensaje actual) y chat (todos los mensajes).

import React, { useEffect, useState } from 'react';
import { initiateSocket, disconnectSocket,
    subscribeToChat, sendMessage } from './Socket';
function App() {
  const rooms = ['A', 'B', 'C'];
  const [room, setRoom] = useState(rooms[0]);
  const [message, setMessage] = useState('');
  const [chat, setChat] = useState([]);
  useEffect(() => {
    if (room) initiateSocket(room);
    subscribeToChat((err, data) => {
      if(err) return;
      setChat(oldChats =>[data, ...oldChats])
    });
    return () => {
      disconnectSocket();
    }
  }, [room]);
return (
    <div>
      <h1>Room: {room}</h1>
      { rooms.map((r, i) =>
        <button onClick={() => setRoom(r)} key={i}>{r}</button>)}
      <h1>Live Chat:</h1>
      <input type="text" name="name" value={message}
        onChange={e => setMessage(e.target.value)} />
      <button onClick={()=> sendMessage(room,message)}>Send</button>
      { chat.map((m,i) => <p key={i}>{m}</p>) }
    </div>
  );
}
export default App;

Aquí se utiliza el gancho más importante useEffect utilizado para la interacción de socket (o cualquier otra llamada externa) .

  • Tiene un array de dependencia [room] que simplemente dice que la función interna se volverá a ejecutar cada vez room que cambie la variable.
  • Dentro de la función interna, iniciará la conexión de socket para un determinado room. Como se discutió antes, llamará initiateSocket y se unirá a la habitación dada.
  • A continuación, registramos una suscripción para el evento requerido y la función de devolución de llamada se ejecuta cada vez que se emite un evento desde el servidor. Esto estará setChat dentro de la devolución de llamada subscribeToChat al cambiar de socket.
  • Finalmente, useEffect hook puede devolver alguna función para limpiar estas interacciones de socket. Aquí disconnectSocket , se desconectaría la conexión del socket para que el servidor no se quedara sin sockets inactivos.

Este enfoque simple hace que las salas de chat funcionen en las que varios clientes que utilizan pestañas del navegador pueden interactuar dentro de sus salas de conexión. Sin embargo, existen pocas preocupaciones cuando se trata de escenarios avanzados.

 

¡Pocas preocupaciones a tener en cuenta!

 

Evite enviar espacio en cada mensaje de chat:

Cada vez que se emitían mensajes de chat, se requería enviar lo que se room pretendía, aunque el socket del cliente se unió a una sala específica antes. Esto se debe al hecho de que socket puede tener varias salas (una por su propia identificación) y se puede almacenar en un servidor en formato socket.rooms . La sala se pasa cada vez para asegurarse de que se transmitirá al grupo de enchufes correcto.

io.on("connection", (socket) => {
  let socketRoom;
  socket.on('join', (room) => {
    socket.join(room);
    socketRoom = room;
  });
  socket.on('chat', (data) => {
    io.to(socketRoom).emit('chat', data.message);
  });
});

 

En el servidor, socket puede mantener información relevante ya sea en el alcance de socket individual o en el alcance global incluido con una identificación única. Esto también puede almacenar cosas individuales como el nombre de usuario, la sala de chat adjunta y demás. Tenga en cuenta que esto debe mantenerse al conectarse, desconectarse o actualizarse y requiere asistencia adicional.

En el ámbito del socket local:

const socketMap = {};
io.on("connection", (socket) => {
socket.on('join', (data) => {
    socket.join(data.room);
    socketMap[socket.id] = data.username;
  });
socket.on('chat', (data) => {
    io.to(data.room).emit('chat',
      { message: data.message, user: socketMap[socket.id] });
  });
});

 

Evite volver a conectarse al cambiar de habitación:

Un inconveniente importante aquí es que al cambiar entre habitaciones, se desconectará el enchufe en la habitación anterior y hará una nueva conexión en la habitación más nueva. Esto agregará una sobrecarga no deseada al cambiar de habitación, pero en su lugar, se puede usar un enchufe conectado existente para cambiar entre habitaciones.

Socket Q9_wYGZtDJVjf7MWAAAI joining A
Disconnected: Q9_wYGZtDJVjf7MWAAAI
Connected: eWn2OGaXaml-0eS6AAAJ
Socket eWn2OGaXaml-0eS6AAAJ joining B

Modificar App.js para cambiar entre habitaciones usando useRef & useEffect hooks:

// Let's keep track of previous room 
const prevRoomRef = useRef();
useEffect(() => {
  prevRoomRef.current = room;
});
const prevRoom = prevRoomRef.current;
// Initiate or Switch Rooms depending on previous and current values
useEffect(() => {
    if (prevRoom && room) switchRooms(prevRoom, room);
    else if (room) initiateSocket(room);
    setChat([]);
    
    // Reset chat messages upon change in room
    // Avoid subscribeToChat as it will duplicate subscriptions.
    // Avoid disconnectSocket as cleanup as socket is reused.
  }, [room]);

// Subscribe only once to event as socket is reused
useEffect(() => {
    subscribeToChat((err, data) => {
      if(err) return;
      setChat(oldChats =>[data, ...oldChats])
    });
}, []);

Emitir switch evento Socket.js con salas relevantes:

export const switchRooms = (prevRoom, nextRoom) => {
  if (socket) socket.emit('switch', { prevRoom, nextRoom });
}

Emitir switch evento Socket.js con salas relevantes:

socket.on('switch', (data) => {
  const { prevRoom, nextRoom } = data;
  if (prevRoom) socket.leave(prevRoom);
  if (nextRoom) socket.join(nextRoom);
  socketRoom = nextRoom;
});

 

Evite el remitente cuando transmita a la sala:

En algunos escenarios, la aplicación en tiempo real puede agregar un mensaje de chat localmente antes de esperar la actualización del socket. Para este enfoque, es mejor si ese mensaje no se recibe al remitente a través de la suscripción de socket, y esto requiere que el mensaje no se transmita al remitente en esa sala.

const socketHistory = {};
io.on("connection", (socket) => {
  let socketRoom;
  socket.on('join', (room) => {
    socket.join(room);
    socketRoom = room;
    socket.emit('joinResponse', socketHistory[room]);
  });
  socket.on('chat', (data) => {
    socket.broadcast.to(socketRoom).emit('chat', data.message);
    socketHistory[socketRoom] = socketHistory[socketRoom] ?
      [data.message, ...socketHistory[socketRoom]] : [data.message]
  });

En la interfaz, suscríbase una vez al joinResponse evento para que cargue los datos iniciales

// Socket.js
export const loadInitialChat = (cb) => {
    if (!socket) return(true)
  
    socket.on('joinResponse', msg => cb(null, msg));
}
// App.js
useEffect(() => {
  loadInitialChat((err, data) => {
    if(err) return;
    setChat(data);
  });
}, []);

 

Este es un tutorial de demostración sobre cómo integrarse fácilmente con salas de socket.io con hooks react js.