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

1.3K
Visualizações
Server's socket.io listener fires twice for no apparent reason

I'm building an app with a chat feature using express.js, react.js and socket.io. Here is a minimal version with the bug reproduced on Github.

The problem is the server's socket.on() always fires twice, for no reason that is apparent to me.

As far as I can tell the client only ever sends a single .emit, but the server's .on always fires twice (and I'm fairly certain that event handler isn't bound twice), regardless of how many clients are connected, or indeed how I've refactored the code.

The sending client's form (input + button) appends the message to its local messages list and then dispatches MESSAGE_NEW to the reducer.

Here's first the Socket.js file.

import React from 'react'
import socketio from 'socket.io-client'

export const socket = socketio.connect('ws://localhost:3001')
export const SocketContext = React.createContext()

And here is the submit handler code from Chat.js (this seems to work correctly, dispatching just once):

const handleSubmit = e => {
  e.preventDefault()

  if (message === '') return

  setMessages(messages => [...messages, message])
  dispatch({ type: 'MESSAGE_NEW', payload: message })
  setMessage('')
}

Here's /Reducer.js, which .emit's the signal (also seems to work fine, firing just once).

import { useContext } from 'react'
import { SocketContext } from './Socket'

export default function Reducer(state, action) {
  const socket = useContext(SocketContext)

  switch (action.type) {
    case 'MESSAGE_NEW':
      // this only fires once, as it should
      console.log('emitting socket')
      socket.emit('message:new', action.payload)
      break

    default:
      return state
  }
}

But here is the relevant bit from /server/index.js, this is where things go wrong. No matter what I've tried, the .on always fires twice.

io.on('connection', socket => {
  console.log('New client connected:', socket.id)

  socket.on('message:new', message => {
    // this always fires twice, i do not understand why
    console.log('message:new: ', message, socket.id)
    socket.broadcast.emit('message:new', message)
  })
})

Update: I found out that moving the socket.emit from Reducer.js to handleSubmit in Chat.js makes it work just fine. I don't understand why since the Reducer doesn't fire twice.

over 4 years ago · Santiago Trujillo
4 Respostas
Responde à pergunta

0

In https://github.com/Heilemann/sockettest/blob/c1df22349ac1877ed625a01b14f48730b7af122d/client/src/Chat.jsx#L14-L22 you add an event listener and later try to remove it again. But since you write out the handler function

message => {
  setMessages([...messages, message])
}

each time, the removal has no effect, since the handler to be removed is not identical to the one that you added. You must assign the handler function to a variable and use that variable in .on and .off.

Even if this does not fix the issue, it is still worth doing.

over 4 years ago · Santiago Trujillo Relatório

0

Your component is rendering twice, calling socket twice, giving two connection messages.

Ensure that it renders once by using your useEffect like this

In your chat.js import the socket.io

import socketio from 'socket.io-client'
let socket
useEffect(() => {
    socket = socketio.connect('ws://localhost:3001')
    return () => {
      socket.off('message:new', listener)
    }
  },[])

In your submit handler

  const handleSubmit = e => {
    e.preventDefault()
    if (message === '') return
    setMessages(messages => [...messages, message])
    socket.emit('message:new', message)
    //dispatch({ type: 'MESSAGE_NEW', payload: message })
    setMessage('')
  }

Passing in an empty array as the second argument means the useEffect will only be called once. The bottom line is to ensure that the socket.io client is called once. props and state changes causes the component to re-render which in turns causes the socket to be connected multiple times. You can explore other patterns that cleaner. However, this is what you need to do.

over 4 years ago · Santiago Trujillo Relatório

0

As per React documentation's on strict mode:

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • Class component constructor, render, and shouldComponentUpdate methods
  • Class component static getDerivedStateFromProps method
  • Function component bodies
  • State updater functions (the first argument to setState)
  • Functions passed to useState, useMemo, or useReducer

Notice the last line: functions passed to useReducer will be invoked twice; that is why your message is being sent twice. You can confirm this by updating your reducer like so:

  case "MESSAGE_NEW":
      alert("I'm running");

You'll notice this alert appearing twice, thanks to React.StrictMode.

If you remove the <React.StrictMode> wrapper in src/App.js the test message will no longer repeat; however, it's not fixing the underlying issue here.

The underlying issue is that you're making your reducer (which should be pure) perform an impure side-effect (in this case, sending a message via a websocket). Additionally, the reducer function also has a useContext call – this is also a no-op from React's perspective and is not good practice.

As you've come across in your question, Simply move the socket.emit into your handleSubmit and you should be good to go. Alternatively, you can consider a more sophisticated setup if you wish to go the reducer route; some popular configurations include Redux + Redux Thunk, or Redux + Redux Saga. Your mileage may vary.

over 4 years ago · Santiago Trujillo Relatório

0

I debugged your code and found the issue with the reducer triggering twice.

The solution is to remove the <React.StrictMode> tags around your App tag in the index.js.

Further info on StrictMode: https://reactjs.org/docs/strict-mode.html

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