const Cup = ({workerRef, symbol, settings, setShowSettings}: CupProps) => {
  const cupParams = useSelector((state: AppState) => state.cupSlice);
  const [dpiScale, setDpiScale] = useState(Math.ceil(window.devicePixelRatio));
  const [canvasSize, setCanvasSize] = useState<CanvasSize>({height: 0, width: 0});
  const containerRef = useRef<HTMLDivElement|null>(null);
  const canvasRef = useRef<HTMLCanvasElement|null>(null);

  useEffect(() => {
    if (!workerRef.current) return;
    let animationFrameId: number|null = null;

    workerRef.current.onmessage = (event: MessageEvent<{
      type: string,
      camera: number,
      aggregation: number,
      bestBidPrice: number,
      bestAskPrice: number,
      maxVolume: number,
      pricePrecision: number,
      quantityPrecision: number,
      priceStep: number,
      cup: {[key: number]: CupItem},
      rowsCount: number,
      volumeAsDollars: boolean,
    }>) => {
      if (event?.data?.type === "update_cup") {
        if (null !== animationFrameId) {
          cancelAnimationFrame(animationFrameId);
        }

        animationFrameId = requestAnimationFrame(() => {
          const context = canvasRef.current?.getContext("2d");
          const zoomedTickSize = event.data.priceStep * event.data.aggregation;

          if (context) {
            const rowsOnScreenCount = cupTools.getRowsCountOnScreen(
              canvasSize.height,
              cupOptions().cell.defaultHeight * dpiScale,
            );
            const realCellHeight = parseInt((canvasSize.height / rowsOnScreenCount).toFixed(0));

            if (event.data.rowsCount !== rowsOnScreenCount) {
              workerRef.current?.postMessage(JSON.stringify({type: "change_rows_count", value: rowsOnScreenCount}));
            }

            cupDrawer.clear(context, canvasSize);

            if (cupParams.rowCount !== rowsOnScreenCount
              || cupParams.cellHeight !== realCellHeight
              || cupParams.aggregation !== event.data.aggregation
            ) {
              dispatch(setCupParams({
                aggregation: event.data.aggregation,
                rowCount: rowsOnScreenCount,
                cellHeight: realCellHeight,
                pricePrecision: event.data.pricePrecision,
                priceStep: event.data.priceStep,
                quantityPrecision: event.data.quantityPrecision,
              }));
            }

            if (event.data.camera === 0 && isLoaded) {
              setIsLoaded(false);
            }

            if (event.data.camera > 0 && !isLoaded) {
              setIsLoaded(true);
            }

            if (event.data.camera !== 0) {
              cupDrawer.draw(
                context,
                canvasSize,
                dpiScale,
                event.data.bestBidPrice,
                event.data.bestAskPrice,
                event.data.maxVolume,
                event.data.pricePrecision,
                event.data.quantityPrecision,
                event.data.priceStep,
                event.data.aggregation,
                rowsOnScreenCount,
                event.data.camera,
                realCellHeight,
                {
                  buy: parseInt((Math.floor(event.data.bestBidPrice / zoomedTickSize) * zoomedTickSize).toFixed(0)),
                  sell: parseInt((Math.ceil(event.data.bestAskPrice / zoomedTickSize) * zoomedTickSize).toFixed(0)),
                },
                darkMode,
                event.data.volumeAsDollars,
                handleClick,
                event.data.cup,
              );
            }
          }
        });
      }
    };
    return () => {
      if (null !== animationFrameId) {
        cancelAnimationFrame(animationFrameId);
      }
    };
  }, [workerRef.current, canvasSize, darkMode, dpiScale, isLoaded]);
 return <div ref={containerRef} className={styles.canvasWrapper}>
    <canvas
      ref={canvasRef}
      className={[styles.canvas, isLoaded ? "" : styles.loading].join(" ")}
      width={canvasSize?.width}
      height={canvasSize?.height}
    />
  </div>;
};

export default Cup;

const cupDrawer = {
  clear: (ctx: CanvasRenderingContext2D, size: CanvasSize) => {
    ctx.clearRect(0, 0, size.width, size.height);
  },
  draw: (
    ctx: CanvasRenderingContext2D,
    size: CanvasSize,
    dpiScale: number,
    bestBidPrice: number,
    bestAskPrice: number,
    maxVolume: number,
    pricePrecision: number,
    quantityPrecision: number,
    priceStep: number,
    zoom: number,
    rowCount: number,
    camera: number,
    cellHeight: number,
    zoomedBestMicroPrice: any,
    darkMode: boolean,
    volumeAsDollars: boolean,
    handleClick: any,
    cup: {[key: number]: CupItem},
  ) => {
    const zoomedTickSize = priceStep * zoom;
    const startMicroPrice = camera + rowCount / 2 * zoomedTickSize - zoomedTickSize;
    const quantityDivider = Math.pow(10, quantityPrecision);
    const priceDivider = Math.pow(10, pricePrecision);

    for (let index = 0; index <= rowCount; index++) {
      const microPrice = startMicroPrice - index * zoomedTickSize;

      if (microPrice < 0) continue;

      const item = cup[microPrice] || {};
      let side = "undefined" !== typeof item?.bid
        ? "bid"
        : "undefined" !== typeof item?.ask ? "ask" : "";

      if ("" === side || (!item?.bid && !item.ask)) {
        side = (microPrice >= (zoomedBestMicroPrice?.buy || zoomedBestMicroPrice?.sell || 0) ? "ask" : "bid");
      }

      const yPosition = cellHeight * index;
      const quantity = volumeAsDollars
        ? ((side === "bid" ? item?.bid || 0 : item?.ask || 0) / quantityDivider) * (microPrice / priceDivider)
        : (side === "bid" ? item?.bid || 0 : item?.ask || 0) / quantityDivider;

      const isBestPrice = cupTools.isBestPrice(
        side,
        microPrice,
        zoomedBestMicroPrice,
      );

      if ((microPrice / zoomedTickSize) % 5 === 0) {
        ctx.beginPath();
        ctx.strokeStyle = darkMode ? "#232425" : "#dddedf";
        ctx.lineWidth = Math.max(1, dpiScale);
        ctx.moveTo(0, yPosition);
        ctx.lineTo(size.width, yPosition);
        ctx.stroke();
      }

      cupDrawer.drawCell(
        ctx,
        quantity,
        side,
        maxVolume,
        isBestPrice,
        yPosition,
        cellHeight,
        size.width,
        dpiScale,
        darkMode,
        handleClick,
      );
    }
  },
  drawCell: (
    ctx: CanvasRenderingContext2D,
    quantity: number,
    side: string,
    maxVolume: number,
    isBestPrice: boolean,
    yPosition: number,
    cellHeight: number,
    fullWidth: number,
    dpiScale: number,
    darkMode: boolean,
    handleClick: any,
  ) => {
    ctx.beginPath();
    ctx.strokeStyle = cupOptions(darkMode).border.color;
    ctx.lineWidth = cupOptions(darkMode).border.width;
    ctx.fillStyle = cupTools.getCellBackground(side, isBestPrice, darkMode);

    // @ts-ignore
    ctx.roundRect(
      0,
      yPosition,
      cupTools.getCellWidth(fullWidth, quantity, maxVolume, isBestPrice),
      cellHeight,
      cupOptions(darkMode).border.radius * dpiScale,
    );

    ctx.canvas.addEventListener("click", (event) => {
      if (event.clientY >= yPosition && event.clientY <= yPosition + cellHeight - 1) {
        console.log("click");
      }
    });
    ctx.fill();
    ctx.stroke();
  },

export default cupDrawer;

по клику на drawCell происходит очень много кликов, draw рисуется много иногда по 100-1000 раз, и по этому клик срабатывает на каждый раз моментально.
Как можно это обойти, чтобы клик срабатывал только один раз