'react-chartjs-2 custom tooltip infinity loop with setState of object

community

I am trying to create custom tooltip with react-chartjs-2, using useState to update top and left as an object, but got infinity loop. However, if use 2 x useState for top and left separately, saw it re-render 3 times for each tooltip hover action.

Can you please help me to understand why would this happen? Thanks in advance.

Here is the error message:

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
    at ChartComponent (http://localhost:3000/static/js/bundle.js:28289:5)
    at ScatterP (http://localhost:3000/static/js/bundle.js:740:80)

Here is my code, this is the example code from react-chartjs-2 scatter plot.

import React from "react";
import {
  Chart as ChartJS,
  LinearScale,
  PointElement,
  LineElement,
  Tooltip,
  Legend,
} from "chart.js";

import { Scatter } from "react-chartjs-2";

ChartJS.register(LinearScale, PointElement, LineElement, Tooltip, Legend);

export const data = {
  datasets: [
    {
      label: "A dataset",
      data: Array.from({ length: 100 }, () => ({
        x: Math.floor(Math.random() * (200 + 1)) - 100,
        y: Math.floor(Math.random() * (200 + 1)) - 100,
      })),
      backgroundColor: "rgba(255, 99, 132, 1)",
      pointHoverRadius: 12,
      radius: 5,
    },
  ],
};

const ScatterP = () => {
  const [isTooltipShow, setIsTooltipShow] = React.useState(false);
  // const [tooltipX, setTooltipX] = React.useState(0); update X and Y separately would be fine.
  // const [tooltipY, setTooltipY] = React.useState(0);
  const [tooltilPosition, setTooltipPosition] = React.useState({
    top: 0,
    left: 0,
  });

  const setTooltipState = (show, x, y) => {
    setIsTooltipShow(show);
    if (x && y) {
      // setTooltipX(x);
      // setTooltipY(y);
      setTooltipPosition({
        top: y,
        left: x,
      });
    }
  };

  const showTooltip = ({ chart, tooltip }) => {
    console.log(tooltip);
    if (tooltip.opacity === 0) {
      setIsTooltipShow(false);
      return;
    }

    const position = chart.canvas.getBoundingClientRect();
    const top = tooltip.caretY + position.y + 5;
    const left = tooltip.caretX + position.x + 5;
    setTooltipState(true, left, top);
    // console.log(position);

    // setIsTooltipShow(true);
    // setTooltipX(left);
    // setTooltipY(top);
  };

  const options = {
    plugins: {
      tooltip: {
        enabled: false,
        external: showTooltip,
      },
    },
    scales: {
      y: {
        beginAtZero: true,
      },
    },
  };

  return (
    <React.Fragment>
      <Scatter options={options} data={data} />
      {isTooltipShow && (
        <div
          className='absolute bg-green-200 border border-2'
          // style={{ top: tooltipY, left: tooltipX }}
          style={tooltilPosition}
        >
          <img
            alt='noimage'
            style={{ width: "500px", height: "600px" }}
            src='https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Image_created_with_a_mobile_phone.png/1200px-Image_created_with_a_mobile_phone.png'
          />
          tooltip content
        </div>
      )}
    </React.Fragment>
  );
};

export default ScatterP;



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source