Search code examples
reactjswebsocketreact-hooksuse-effectuse-state

Adding data in React component via websocket


I'm trying to add a data to a table of some operations in React via WebSockets. I even get the new data from WebSocket successfully. I stuck with the question how to add a new data to existing. When I recieve new data in websocket response, my const operationList becomes empty.

Have a look at my code:

const [operationsList, setOperationsList] = useState([{}] )

// Here I get the existing data from backend API and store to operationsList. It works
  async function fetchOperations(activeID) {
    if (activeID !== false) {
      const response = await axios.get(
        `http://127.0.0.1:8000/api/operations/?made_by=${activeID}`
      )
      setOperationsList(response.data)
    }
  }
  useEffect(() => {
    setIsOperationsLoading(true)
    fetchOperations(activeID)
      .then(() => {setIsOperationsLoading(false)})
      .catch((e) => {setOperationsError(e)})
  },[activeID])


// Here I subscribe to websockets to get new data for adding to operationsList
  useEffect(() => {
    const ws = new WebSocket('ws://127.0.0.1:8000/ws/')
    ws.addEventListener('message', (e) => {
      const response = JSON.parse(e.data)
      console.log(response) // Here I see new data. It's ok
      console.log(operationsList) // All of the sudden operationsList become empty
    })
    ws.onopen = () => {
      ws.send(JSON.stringify({
      action: "subscribe_to_operations_activity",
      request_id: new Date().getTime(),
    }))
    }
  }, [])

I thought that in my second useEffect I could just add response data from WebSocket like setOperationsList([response, operationsList]). But operationsList is empty, so I've got just a new data in the table. How to fix it?


Solution

  • The second useEffect hook runs only once when the component mounts, you are logging the initial operationsList state value closed over in callback scope. In other words, it's a stale enclosure over the operationsList state.

    I'm guessing it's at this point you are wanting to append response to the operationsList state. You can use a functional state update to correctly access the previous state and append to it.

    You may also want to unsubscribe to the "message" event in the useEffect hook's cleanup function. This is to prevent resource leaks and attempts to update state of unmounted components.

    useEffect(() => {
      const ws = new WebSocket('ws://127.0.0.1:8000/ws/');
    
      const handler = (e) => {
        const response = JSON.parse(e.data);
    
        setOperationsList(operationsList => [
          ...operationsList, // <-- shallow copy previous state
          response,          // <-- append new data
        ]);
      };
    
      ws.addEventListener('message', handler);
    
      ws.onopen = () => {
        ws.send(JSON.stringify({
          action: "subscribe_to_operations_activity",
          request_id: new Date().getTime(),
        }));
      };
    
      return () => {
        ws.removeEventListener('message', handler);
      };
    }, []);