Z góry dziękuję za wszelkie sugestie. Wdrażam tu Websocket w ReactJS i moim celem jest zapełnienie mojego stanu historii nadchodzącymi zdarzeniami „onmessage”. Co dziwne dla mnie, setHistory opróżnia moją tablicę po odebraniu wiadomości ze zdarzeń „websocket.onmessage”. Usuwa nawet mój stan początkowy. Uproszczona wersja poniżej:

function NotificationWebSocket ({object_id}) {
const [history, setHistory] = useState([{text: "The first notification"}]) //

    function connectToWebSocket() {
        const wsStart = (window.location.protocol === "https:" ? "wss://" : "ws://")
        const url = 'localhost:8000/live/'
        let socket = new ReconnectingWebSocket(wsStart + url + object_id)
        socket.onmessage = e => {
            console.log(e.data) // logs the received data correctly
            setHistory([...history, {text: e.data}]) // ?? this is setting my history state array to null... ??
            setHistory(history.append({text: e.data})) //also cleans my history array here.
        }
    }

    useEffect(() => {
        connectToWebSocket()
    },[]) // so that we only connect to the websocket once

    return (
        <div>
            We are on a live connection to the server.
            {history.map(item=> <div>{item.text}</div>)}
        </div>
    )
}

Rzeczy do zapamiętania: używam biblioteki ReconnectingWebSocket, chociaż próbowałem jej nie używać i dzieje się to samo. Moje connectToWebSocket jest wyzwalane z wnętrza metody useEffect, w przeciwnym razie otrzymuję kilka żądań Websocket na serwerze.

Jeszcze raz dziękuję, Felipe.

3
Felipe de Abreu Prazeres 8 marzec 2020, 17:57

2 odpowiedzi

Najlepsza odpowiedź

Masz przestarzałe zamknięcie, jest to częsty błąd, który jest często popełniany podczas używania punktów zaczepienia z zależnościami (takich jak useEffect, useMemo, useCallback). Zajrzyj tutaj.

Rozwiązaniem jest zawsze uwzględnianie wszystkich zmiennych, których będziesz używać w wywołaniu zwrotnym useEffect na liście zależności . W twoim przypadku trudno je dostrzec, ponieważ większość z nich jest ukryta wewnątrz funkcji connectToWebSocket, która jest aktualizowana przy każdym renderowaniu, z wyjątkiem ... tego wywoływanego przez useEffect jest tym, który został utworzony na pierwszy render, a wywołanie zwrotne onmessage zamyka stan, który komponent miał przy pierwszym renderowaniu.

Ponieważ używasz WebSocket, nie wystarczy po prostu useEffect za każdym razem, gdy history się zmienia, ponieważ wtedy będziesz mieć wiele połączeń (jedno na każdą zmianę), musisz też rozłączyć się przy następnej wywoływany jest efekt i możesz to zrobić, zwracając funkcję z wywołania zwrotnego useEffect, która rozłącza się z WebSocket, tak aby efekt mógł się ponownie połączyć. Jest to również przykład, który jest koncepcyjnie wyjaśniony w dokumentach Reacta, kiedy pokazuje przykład o useEffect.

Często sugeruje się, aby uniknąć tego rodzaju sytuacji

  1. Zdefiniuj funkcję, którą zamierzasz wywołać w wywołaniu zwrotnym useEffect w samym wywołaniu zwrotnym, zamiast poza nim;
  2. Użyj swojego ulubionego lintera, aby ostrzec Cię o brakujących zależnościach w Twoich useEffect (lub podobnych) połączeniach. Jeśli spróbujesz create-react-app, to jest już skonfigurowane.

Ponadto, jak sugeruje @ ako-javakhishvili, za każdym razem, gdy twój następny stan zależy od poprzedniej wartości samego stanu, wywołaj funkcję ustawiającą z wywołaniem zwrotnym zamiast wartości bezpośredniej. To rozwiąże Twój problem w tym przypadku, ale nadal będziesz mieć nieaktualne zamknięcie i również powinieneś się tym zająć.

1
Gian Marco Toso 8 marzec 2020, 15:16

Możesz po prostu zmienić setHistory([...history, {text: e.data}]) za pomocą następującego kodu setHistory(history=> [...history, {text: e.data}])

4
Aleksandre Javakhishvili 8 marzec 2020, 15:06