import {useCallback, useEffect, useReducer, useState} from "react";
import {useDispatch} from "react-redux";
import {setFuturesBinancePriceState, setSpotBinancePriceState} from "../store/binancePriceSlice";

export enum ReadyState {
  UNINSTANTIATED = -1,
  CONNECTING = 0,
  OPEN = 1,
  CLOSING = 2,
  CLOSED = 3,
}

export function useBinancePrice() {
  const stream = "@ticker";
  const dispatch = useDispatch();
  const [futuresWs, setFuturesWs] = useState<WebSocket|null>(null);
  const [futuresReadyState, setFuturesReadyState] = useState(0);
  const [spotWs, setSpotWs] = useState<WebSocket|null>(null);
  const [spotReadyState, setSpotReadyState] = useState(0);

  const connectToWs = () => {
    if ("undefined" === typeof window) {
      setTimeout("connectToWs", 1000);

      return;
    }

    setFuturesWs(new WebSocket("wss://fstream.binance.com/stream"));
    setSpotWs(new WebSocket("wss://stream.binance.com:9443/stream"));
  };

  const futuresSubscribe = useCallback((symbol: string) => {
    if (null === futuresWs || futuresReadyState !== ReadyState.OPEN) return;

    futuresWs.send(JSON.stringify({
      id: ((new Date()).getTime() + Math.random() * 10000).toFixed(0),
      method: "SUBSCRIBE",
      params: [
        `${symbol}${stream}`,
      ],
    }));
  }, [futuresReadyState]);

  const futuresUnsubscribe = useCallback((symbol: string) => {
    if (null === futuresWs || futuresReadyState !== ReadyState.OPEN) return;

    futuresWs.send(JSON.stringify({
      id: ((new Date()).getTime() + Math.random() * 10000).toFixed(0),
      method: "UNSUBSCRIBE",
      params: [
        `${symbol}${stream}`,
      ],
    }));
  }, [futuresReadyState]);

  useEffect(() => {
    if (null === futuresWs) return;

    futuresWs.onmessage = (message: MessageEvent) => {
      if (!message.data) return;

      const data = JSON.parse(message.data);

      if (!data?.stream) return;

      dispatch(setFuturesBinancePriceState({symbol: data.stream.replace(stream, ""), price: data.data.c}));
    };

    futuresWs.onopen = () => {
      setFuturesReadyState(ReadyState.OPEN);
    };

    futuresWs.onclose = () => {
      setFuturesReadyState(ReadyState.CLOSED);
    };
  }, [futuresWs]);

  const spotSubscribe = useCallback((symbol: string) => {
    if (null === spotWs || spotReadyState !== ReadyState.OPEN) return;

    spotWs.send(JSON.stringify({
      id: ((new Date()).getTime() + Math.random() * 10000).toFixed(0),
      method: "SUBSCRIBE",
      params: [
        `${symbol}${stream}`,
      ],
    }));
  }, [spotReadyState]);

  const spotUnsubscribe = useCallback((symbol: string) => {
    if (null === spotWs || spotReadyState !== ReadyState.OPEN) return;

    spotWs.send(JSON.stringify({
      id: ((new Date()).getTime() + Math.random() * 10000).toFixed(0),
      method: "UNSUBSCRIBE",
      params: [
        `${symbol}${stream}`,
      ],
    }));
  }, [spotReadyState]);

  useEffect(() => {
    if (null === spotWs) return;

    spotWs.onmessage = (message: MessageEvent) => {
      if (!message.data) return;

      const data = JSON.parse(message.data);

      if (!data?.stream) return;

      dispatch(setSpotBinancePriceState({symbol: data.stream.replace(stream, ""), price: data.data.c}));
    };

    spotWs.onopen = () => {
      setSpotReadyState(ReadyState.OPEN);
    };

    spotWs.onclose = () => {
      setSpotReadyState(ReadyState.CLOSED);
    };
  }, [spotWs]);

  useEffect(() => {
    connectToWs();
  }, []);

  return {
    futuresSubscribe,
    futuresUnsubscribe,
    futuresReadyState,
    spotSubscribe,
    spotUnsubscribe,
    spotReadyState,
  };
}

import {createSlice} from "@reduxjs/toolkit";
import {HYDRATE} from "next-redux-wrapper";

export interface BinancePriceSlice {
  futures: {[key: string]: number};
  spot: {[key: string]: number};
}

const initialState: BinancePriceSlice = {
  futures: {},
  spot: {},
};

export const binancePriceSlice = createSlice({
  name: "binancePrice",
  initialState,
  reducers: {
    setFuturesBinancePriceState(state, action) {
      state.futures[action.payload.symbol] = action.payload.price;
    },
    setSpotBinancePriceState(state, action) {
      state.spot[action.payload.symbol] = action.payload.price;
    },
  },
  extraReducers: {
    [HYDRATE]: (state, action) => {
      return {
        ...state,
        ...action.payload,
      };
    },
  },
});

export const {
  setFuturesBinancePriceState,
  setSpotBinancePriceState,
} = binancePriceSlice.actions;

export default binancePriceSlice.reducer;

Надо разобраться с подключением к вебсокетам бинанса на фронте. Они через раз срабатывают