feverdreme
feverdreme

Reputation: 527

How to prevent react state overwrites due to asynchronous code and websocket race condition?

I'm building a react app with recoiljs as my state manager and I need to fetch data from a web socket and update the state accordingly. The reason I'm not using a regular array is that I need to share state across my components.

To elaborate, the component has a WebSocket connection and will have data streaming into it. It then needs to update an array by pushing the received data. The problem is due to the component lifecycle or something, the state gets written incorrectly and this is what I think happens

  1. Response 1 gets received and setState([...state, data]) is called
  2. Response 2 then gets received and setState([...state, data]) is called
  3. The state is actually set by Response 1
  4. The state is set by Response 2

The problem is when response 2 comes in and setState is called, it has an old version of the state that doesn't include Response 1.

I can't seem to figure out how to fix this. Here is the stripped-down code.

import {atom} from "recoil"
import {useRecoilState} from "recoil"
const WebSocket = require("isomorphic-ws");

const statusAtom = atom<string[]>({
    key: "statusAtom",
    default: []
});


function Component() {
    const [status, setStatus] = useRecoilState(statusAtom);

    const functionCalledAfterButtonPress = () => {
        ws.onmessage = function incoming(res: any) {
            console.log(res.data); // for debugging
            
            setStatus([...status, res.data]);
        };
    }
}

Note that I am not including things other events like ws.onopen and return statements or whatnot and that the atom declaration and component declaration are in different files.

Upvotes: 2

Views: 1653

Answers (1)

feverdreme
feverdreme

Reputation: 527

As Jayce444 said, the way to solve this is instead of using setStatus with an already computed value, [...status, res.data], I should instead pass in a lambda statement that is called when the state is updated.

Instead of: setStatus([..status, res.data])

Use: setStatus(prevStatus => [...prevStatus, res.data])

Upvotes: 4

Related Questions