Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

409
Visualizações
Socket.io connection intact but React component that never unmounts becomes unresponsive to socket events after page navigation

Setup:

  • Socket.io in React with react-router-dom
  • Socket instance passed to child components via useContext
  • Backend built using Express

Problem:
Navbar listens for socket events to update counters for unread messages. Everything works fine until user navigates to a different page. Once a navigation occurs, the socket in navbar (which never unmounts) is unresponsive to future events being emitted from the server even though the connection is intact.

Expected behaviour:
Navbar socket remains responsive to emits from server after page navigation.

Observation:
Other areas of the app where socket is utilized remains functional even while navbar socket is unresponsive. The navbar socket becomes responsive again when page is refreshed but the same problem repeats after page navigation. The navbar is the only component that doesn't unmount when a navigation occurs while components mounted on navigation is loaded with the socket instance via useContext.

As far as I can see, connection is never broken (disconnect event never fires on server-side) and the newly mounted component can emit an event, server responds, and emits a response back to client where the new component responds while the navbar doesn't.

Other notes:
This is the first question I've ever asked so my apologies in advance if the question is poorly formatted. The code is obviously simplified to leave out areas where it seems to not affect the problem. The server-side code is omitted because it seems to receive and emit events without any problems.

//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>
  )
}
over 4 years ago · Santiago Trujillo
2 Respostas
Responde à pergunta

0

Since your socket wants to live from first page load until you close the page (I assume so..) I suggest to decouple socket initialisation from React. It just doesn't seem right to put it in a ref because even the app component has still a chance to unmount and mount again (as you experienced on page load) or to do other sideeffects to it.

For example have a file 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);

Then in your APP.js do like so:

import React from "react";
import SocketContext, { socket } from "./socket";

const App = () => (
   <SocketContext.Provider value={socket}>
      <OtherComponents />
   </SocketContext.Provider>
);

Or use it in any other file 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)
   })
}

You can adapt this to maybe only initialise socket after login is succeded..

I have this in plenty of projects and I never experienced such headaches problems with this solution - KEEP IT SIMPLE.

over 4 years ago · Santiago Trujillo Relatório

0

Issue

You are not disconnecting/unsubscribing the sockets correctly. socket.off takes two arguments, the second is the listener callback you want to disconnect/unlisten/unsubscribe from the event.

socket.off(eventName, listener)

Removes the specified listener from the listener array for the event named eventName.

const listener = (...args) => {
  console.log(args);
}

socket.on("details", listener);

// and then later...
socket.off("details", listener);

Solution

Refactor the listeners into standalone functions that can be passed to both the socket.on and 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]);

  ...
}

Messages.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);

  ...
}

I agree with Silven in that the socket should probably be moved out of the parent React component code. Instantiate it once and set as the context value as a singleton.

over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda