Socket.io using old state value in React Functional Components

Viewed 109

Below is the code I originally ran with. As far as I can tell this code is functional if the component re-renders and reconnects the socket, but not on initial load. I've got as far as determining that the state is updated nominally when I'm using the local values from a child component calling handleUpdateCell, but it does not seem to be possible to update the value from socket.on().

interface MyComponentProps {
    gameID: number;
}

export const MyComponent: React.FC<MyComponentProps> = (props) => {
    const params = useParams<GameRouteParams>();
    const gameID = parseInt(params.gameID!);
    const [gameName, setGameName] = useState<string>();
    const [positions, setPositions] = useState<ICell[]>();
    const [lastUpdate, setLastUpdate] = useState<ICell>();
    const [pawns, setPawns] = useState<Record<string, IPawn>>();
    const [isLoading, setIsLoading] = useState(false);
const socket = useMemo(() => {
            const ENDPOINT = `http://localhost`;
            const APIPATH = `/api/source`;
            return socketIOClient(ENDPOINT, {
                path: APIPATH,
                transports: ["websocket"], //TODO: I don't like this option atm, it means I'm misunderstanding something about CORS to get this far, but I just want to move forward
                query: {
                    gameID: gameID.toString(),
                },
            });
}, [gameID]);
const handleUpdateCell: HandleUpdateCell = (id, x, y, clientDrop) => {
    console.log({ positions: positions }); //THIS IS ALWAYS undefined (initial state)FROM SOCKET.ON('map-update')
    if (positions) {
        const positionUpdate = [...positions];
        const positionIndex = positionUpdate.findIndex(
            (cell) => cell.id === id
        );
        if (positionIndex > -1) {
            const theCell = positionUpdate[positionIndex];
            theCell.x = x;
            theCell.y = y;
            positionUpdate[positionIndex] = theCell;
            setLastUpdate(theCell);
            setPositions(positionUpdate);
            if (clientDrop && socket) {
                socket.emit(`token-update:${gameID}`, theCell);
            }
        }
    }
};

const handleMapUpdate = (cellUpdate: { positions: ICell[] }) => {
    if (cellUpdate && cellUpdate.positions) {
        for (let i = 0; i < cellUpdate.positions.length; i++) {
            const cell = cellUpdate.positions[i];
            handleUpdateCell(cell.id, cell.x, cell.y, false);
        }
    }
};

useEffect(() => {
    //connect via websockets to api
    socket.on("connect", () => {
        console.log("Connect");
        socket.onAny((eventName, args) => {
            console.log({ eventName, args });
        });
        socket.on(`game-init:${gameID}`, (gameInfo: IGameState) => {
            console.log("Game Init");
            console.log(gameInfo);
            setGameName(gameInfo.name);
            setPositions(gameInfo.positions);
            setPawns(gameInfo.pawns);
            setIsLoading(false);
        });
        socket.on(`map-update:${gameID}`, handleMapUpdate);
    });
}, [gameID, socket, handleMapUpdate]);
useEffect(() => {
    console.log({ Title: "Position useEffect", positions: positions });
}, [positions]);

It seems that the old state is captured so I tried adding useCallback:

const handleUpdateCell: HandleUpdateCell = useCallback((id, x, y, clientDrop) => {
    console.log({ positions: positions });
    if (positions) {
        const positionUpdate = [...positions];
        const positionIndex = positionUpdate.findIndex(
            (cell) => cell.id === id
        );
        if (positionIndex > -1) {
            const theCell = positionUpdate[positionIndex];
            theCell.x = x;
            theCell.y = y;
            positionUpdate[positionIndex] = theCell;
            setLastUpdate(theCell);
            setPositions(positionUpdate);
            if (clientDrop && socket) {
                socket.emit(`token-update:${gameID}`, theCell);
            }
        }
    }
}, [positions, gameID]);

const handleMapUpdate = useCallback((cellUpdate: { positions: ICell[] }) => {
    if (cellUpdate && cellUpdate.positions) {
        for (let i = 0; i < cellUpdate.positions.length; i++) {
            const cell = cellUpdate.positions[i];
            handleUpdateCell(cell.id, cell.x, cell.y, false);
        }
    }
}, [
    handleUpdateCell
]);

Unfortunately this does not seem to do the trick. I've also tried useRef on positions but that was also a bust. I assume I'm making a very basic error but I'm on my 3rd hour of debugging just this one problem.

0 Answers
Related