Configuración:
Problema:
La barra de navegación escucha eventos de socket para actualizar los contadores de mensajes no leídos. Todo funciona bien hasta que el usuario navega a una página diferente. Una vez que se produce una navegación, el socket en la barra de navegación (que nunca se desmonta) no responde a los eventos futuros que se emiten desde el servidor aunque la conexión esté intacta.
Comportamiento esperado:
El socket de la barra de navegación sigue respondiendo a las emisiones del servidor después de la navegación por la página.
Observación:
Otras áreas de la aplicación donde se utiliza el socket siguen funcionando incluso cuando el socket de la barra de navegación no responde. El socket de la barra de navegación vuelve a responder cuando se actualiza la página, pero el mismo problema se repite después de la navegación de la página. La barra de navegación es el único componente que no se desmonta cuando se produce una navegación mientras los componentes montados en la navegación se cargan con la instancia de socket a través de useContext.
Por lo que puedo ver, la conexión nunca se interrumpe (el evento de desconexión nunca se activa en el lado del servidor) y el componente recién montado puede emitir un evento, el servidor responde y emite una respuesta al cliente donde el nuevo componente responde mientras la barra de navegación no 't.
Otras notas:
Esta es la primera pregunta que hago, así que pido disculpas de antemano si la pregunta tiene un formato deficiente. El código obviamente está simplificado para dejar fuera áreas donde parece no afectar el problema. El código del lado del servidor se omite porque parece recibir y emitir eventos sin ningún problema.
//App.js const App = () => { const socket = useRef(io('path')); return ( <SocketContext.Provider value={socket.current}> <BrowserRouter> <Nav /> <Switch> <Route path='/messages' component={Messages} /> <Route path='/' component={Home} /> <!-- Other routes --> </Switch> </BrowserRouter> </SocketContext.Provider> ) } //Nav.js const Nav = () => { const socket = useContext(SocketContext); const [newMsgCount, setNewMsgCount] = useState(0); useEffect(() => { socket.emit('get new msg count'); socket.on('new msg count', (count) => setNewMsgCount(count)); socket.on('new msg received', () => setNewMsgCount(prev => prev + 1)); socket.on('marked as read', () => setNewMsgCount(prev => prev - 1)); return () => { socket.off('new msg count'); socket.off('new msg received'); socket.off('marked as read'); } }, [socket]); return ( //Omitted for brevity ) } //Messages.js const Messages = () => { const socket = useContext(SocketContext); const [msgs, setMsgs] = useState([]); const [newMsg, setNewMsg] = useState({ to: '', body: '' }); useEffect(() => { socket.emit('get all msgs'); socket.on('all msgs', (data) => setMsgs(data)); socket.on('new msg received', (data) => setMsgs(prev => ([...prev, data]))); return () => { socket.off('all msgs'); socket.off('received new msg'); } }, [socket]); const send = () => socket.emit('new msg', newMsg); return ( <div> <!-- Omitted for brevity --> <form onSubmit={send}> <!-- Omitted for brevity --> <button>Send</button> </form> </div> ) }Dado que su socket quiere vivir desde la carga de la primera página hasta que cierre la página (supongo que sí ...) sugiero desacoplar la inicialización del socket de React . Simplemente no parece correcto ponerlo en una ref porque incluso el componente de la aplicación todavía tiene la posibilidad de desmontarse y volver a montarse (como experimentó en la carga de la página) o tener otros efectos secundarios.
Por ejemplo, tenga un archivo socket.js
import React from "react"; import io from "socket.io-client"; // have a standalone variable holding the socket. const path = '...' export const socket = io(path); export const SocketContext = React.createContext(socket); Luego, en su APP.js , haga lo siguiente:
import React from "react"; import SocketContext, { socket } from "./socket"; const App = () => ( <SocketContext.Provider value={socket}> <OtherComponents /> </SocketContext.Provider> ); O utilícelo en cualquier otro archivo my-outsourced-mapping.js
import { socket } from "./socket"; // say we have redux import store from './store' export const veryLargeMappingFunctionForThisParticularShittyEventMyBackendGuyDid() { socket.on('complex-data-event', data => { mapped = // reduce, map, group and more store.dispatch('simple-data-event', mapped) }) }Puede adaptar esto para tal vez solo inicializar el socket después de que se haya iniciado sesión correctamente.
Tengo esto en muchos proyectos y nunca experimenté tales problemas de dolores de cabeza con esta solución: MANTÉNGALO SIMPLE.
No estás desconectando/dando de baja los enchufes correctamente. socket.off toma dos argumentos, el segundo es la devolución de llamada del oyente que desea desconectar/dejar de escuchar/darse de baja del evento.
socket.off(nombreEvento, oyente)
Elimina el agente de escucha especificado de la matriz de agentes de escucha para el evento denominado eventName.
const listener = (...args) => { console.log(args); } socket.on("details", listener); // and then later... socket.off("details", listener);
Refactorice los oyentes en funciones independientes que se pueden pasar tanto a socket.on como socket.off .
Nav.js
const Nav = () => { const socket = useContext(SocketContext); const [newMsgCount, setNewMsgCount] = useState(0); useEffect(() => { socket.emit('get new msg count'); const incrementCount = () => setNewMsgCount(prev => prev + 1); const decrementCount = () => setNewMsgCount(prev => prev - 1); socket.on('new msg count', setNewMsgCount); socket.on('new msg received', incrementCount); socket.on('marked as read', decrementCount); return () => { socket.off('new msg count', setNewMsgCount); socket.off('new msg received', incrementCount); socket.off('marked as read', decrementCount); } }, [socket]); ... }Mensajes.js
const Messages = () => { const socket = useContext(SocketContext); const [msgs, setMsgs] = useState([]); const [newMsg, setNewMsg] = useState({ to: '', body: '' }); useEffect(() => { socket.emit('get all msgs'); const messageRecdHandler = (data) => setMsgs(prev => ([...prev, data])); socket.on('all msgs', setMsgs); socket.on('new msg received', messageRecdHandler); return () => { socket.off('all msgs', setMsgs); socket.off('received new msg', messageRecdHandler); } }, [socket]); const send = () => socket.emit('new msg', newMsg); ... }Estoy de acuerdo con Silven en que el socket probablemente debería sacarse del código principal del componente React. Crea una instancia una vez y configúralo como valor de contexto como singleton.