Forma correcta de usar ganchos React + WebSockets

4 minutos de lectura

Necesito conectarme al servidor WebSockets y registrar sus mensajes. Con el componente de clase React, pondría esta lógica en componentDidMount gancho de ciclo de vida y seguir adelante felizmente, pero no estoy seguro de cómo implementarlo correctamente con ganchos.

Aquí está mi primer intento.

import React, {useEffect} from 'react';

export default function AppWs() {
  useEffect(() => {
    let ws = new WebSocket('wss://ws.kraken.com/');
    ws.onopen = () => console.log('ws opened');
    ws.onclose = () => console.log('ws closed');

    ws.onmessage = e => {
      const message = JSON.parse(e.data);
      console.log('e', message);
    };

    return () => {
      ws.close();
    }
  }, []);

  return (
    <div>hooks + ws</div>
  )
}

Agregué conexión y lógica de registro a useEffect, proporcionó una matriz vacía con dependencias, y todo funcionó muy bien. Hasta que tuve que agregar pause estado para pausar el registro.

export default function AppWs() {
  const [isPaused, setPause] = useState(false);

  useEffect(() => {
    let ws = new WebSocket('wss://ws.kraken.com/');
    ws.onopen = () => console.log('ws opened');
    ws.onclose = () => console.log('ws closed');

    ws.onmessage = e => {
      if (isPaused) return;
      const message = JSON.parse(e.data);
      console.log('e', message);
    };

    return () => {
      ws.close();
    }
  }, []);

  return (
    <div>
      <button onClick={() => setPause(!isPaused)}>{isPaused ? 'Resume' : 'Pause'}</button>
    </div>
  )
}

ESLint comenzó a gritarme que debería agregar isPaused estado como una dependencia de useEffect.
Bueno, está bien, hecho.
Pero noté la reconexión al servidor WS cada vez que hago clic en el botón. Esto claramente no es lo que quiero.

Mi siguiente iteración fue usar dos useEffects: uno para conexión y otro para procesamiento de mensajes.

export default function AppWs() {
  const [isPaused, setPause] = useState(false);
  const [ws, setWs] = useState(null);

  useEffect(() => {
    const wsClient = new WebSocket('wss://ws.kraken.com/');
    wsClient.onopen = () => {
      console.log('ws opened');
      setWs(wsClient);
    };
    wsClient.onclose = () => console.log('ws closed');

    return () => {
      wsClient.close();
    }
  }, []);

  useEffect(() => {
    if (!ws) return;

    ws.onmessage = e => {
      if (isPaused) return;
      const message = JSON.parse(e.data);
      console.log('e', message);
    };
  }, [isPaused, ws]);

  return (
    <div>
      <button onClick={() => setPause(!isPaused)}>{isPaused ? 'Resume' : 'Pause'}</button>
    </div>
  )
}

Esto funciona como se esperaba, pero tengo la sensación de que me falta algo y esta tarea se puede resolver más fácilmente, con uno useEffect. Ayúdeme a refactorizar el código para convencerme de que estoy usando los ganchos React de manera adecuada. ¡Gracias!

Avatar de usuario de Álvaro
Álvaro

Como solo está configurando el socket web una vez, creo que un mejor enfoque es usar una referencia en lugar de un estado:

El orden de useEffect es importante.

Como sugiere George en los comentarios, en la primera useEffect ws.current se guarda en una variable para asegurarse de que cuando close se llama se refiere a la misma instancia.

export default function AppWs() {
    const [isPaused, setPause] = useState(false);
    const ws = useRef(null);

    useEffect(() => {
        ws.current = new WebSocket("wss://ws.kraken.com/");
        ws.current.onopen = () => console.log("ws opened");
        ws.current.onclose = () => console.log("ws closed");

        const wsCurrent = ws.current;

        return () => {
            wsCurrent.close();
        };
    }, []);

    useEffect(() => {
        if (!ws.current) return;

        ws.current.onmessage = e => {
            if (isPaused) return;
            const message = JSON.parse(e.data);
            console.log("e", message);
        };
    }, [isPaused]);

    return (
        <div>
            <button onClick={() => setPause(!isPaused)}>
                {isPaused ? "Resume" : "Pause"}
            </button>
        </div>
    );
}

  • cuando uso este código, noto que el websocket se cierra y luego se vuelve a abrir después de cada mensaje recibido. ¿Alguna idea de por qué está haciendo eso?

    – Rykener

    27 sep 2020 a las 23:49

  • Por cierto, debe almacenar el websocket recién creado en una variable separada y usarlo para la limpieza. No la ws.current. Algún otro código puede mutar el ws.current y la antigua instancia de websocket no se cerrará con la limpieza. La conexión permanecerá abierta y causará errores.

    – Jorge

    16 de julio de 2021 a las 11:53

  • @Alvaro: espero que puedas ver el hilo stackoverflow.com/questions/69476080/…

    – ArthurJ

    7 de octubre de 2021 a las 5:55

  • @Alvaro, también utilicé su código exacto para resolver mis problemas de websockets. Gracias 🙂 Pero, ¿por qué useRef? Leí los documentos y se supone que debe usarse para hacer referencia a un elemento DOM. Pero aquí este no es el caso. ¿Alguien podría explicar cómo se está utilizando aquí?

    – Greg Woods

    17 de noviembre de 2021 a las 8:10

  • Encontré una buena respuesta a mi propia pregunta aquí: dmitripavlutin.com/react-useref-guide Aparentemente, las referencias no son solo para elementos DOM. Los puntos clave son que persisten entre actualizaciones de componentes y no desencadenan una actualización de componentes cuando se cambia el valor. Ideal para este escenario.

    – Greg Woods

    17 de noviembre de 2021 a las 20:29

 useEffect(() => {
const socket = new WebSocket('wss://ws.kraken.com');
socket.addEventListener('message', function (event) {
  const a = JSON.parse(event.data);
  setPriceWebSocket(a);

  const amin = socket.send(
    JSON.stringify({
      event: 'subscribe',
      pair: ['XBT/USD', 'XBT/EUR', 'ADA/USD'],
      subscription: { name: 'ticker' },
    }),
  );
  

});

  • Como está escrito actualmente, su respuesta no está clara. Edite para agregar detalles adicionales que ayudarán a otros a comprender cómo esto aborda la pregunta formulada. Puede encontrar más información sobre cómo escribir buenas respuestas en el centro de ayuda.

    – Comunidad
    Bot

    6 jun a las 14:47

¿Ha sido útil esta solución?