React - accessing an array item returns the old value, despite the log being correct

47 views Asked by At

I am trying to use ChartJS, and make it possible for my labels to be editable updating my data live. Meaning all the labels on the x-axis and the value labels above the chart bar will be editable live changing the data for the chart.

The problem I am facing here, is that the state responsible for the position of inputs responsible for the value of bars is "one step behind". I have to use getDatasetMeta to access the metadata of the chart, where I get the positions and sizes of each bars. Based on the height of each bar, I calculate the position of the label. The problem is, that when looping through the metadata, I always get back the old value, until I change the next input or change my code. In the code, you can see uncommented logs - the one logging the metadata prints the correct values, however, the one pointing to a particular value meta[0].y returns the old one. This is super weird. I tried setting metadata as state variable and using [...] to get the copy of the object, and also using multiple useEffects. Nothing of these worked. Any help? Thank you very much.

import { useState, useEffect } from "react";
import { useImmer } from "use-immer";

import React, { MouseEvent, useRef } from "react";
import {
  Chart as ChartJS,
  LinearScale,
  CategoryScale,
  BarElement,
  PointElement,
  LineElement,
  Legend,
  Tooltip,
  Plugin,
} from "chart.js";
import {
  Chart,
  getDatasetAtEvent,
  getElementAtEvent,
  getElementsAtEvent,
} from "react-chartjs-2";

ChartJS.register(
  LinearScale,
  CategoryScale,
  BarElement,
  PointElement,
  LineElement,
  Legend,
  Tooltip
);
export const options = {
  responsive: true,
  plugins: {
    legend: {
      position: "top" as const,
    },
    title: {
      display: true,
      text: "Chart.js Bar Chart",
    },
    datalabels: {
      color: "black",
      font: {
        weight: "bold", 
      },
      align: "top",
      anchor: "end",
      display: true, 
    },
  },
};

const ChartRender = () => {
  const [data, setData] = useImmer({
    labels: ["January", "February", "March", "April", "May", "June", "July"],
    datasets: [
      {
        label: "Dataset 1",
        data: [50, 150, 600, 500, 430, 600, 200],
        backgroundColor: "rgba(255, 99, 132, 0.5)",
      },
    ],
  });
  const [wasDataUpdated, setWasDataUpdated] = useState(false);
  const [nothingFirstElDifference, setNothingFirstElDifference] = useState(0);
  const [elDifference, setElDifference] = useState(0);
  const [valueYPosList, setValueYPosList] = useState([0]);

  const chartRef = useRef<ChartJS>(null);

  function changeFirstValue(value, index) {
    setData((draft) => {
      draft.datasets[0].data[index] = value;
    });
    setWasDataUpdated(!wasDataUpdated);
  }

  const executeAfterRender = () => {
    let heightValuesArray = [];
    const chart = chartRef.current;
    if (!chart) return;
    let height = 261;
    let meta = chart.getDatasetMeta(0)["data"];

    setNothingFirstElDifference(meta[0].x * 0.50059774);
    setElDifference(meta[2].x - meta[1].x);
    for (let i = 0; i < meta.length; i++) {
      heightValuesArray.push(height - meta[i].y);
      console.log(meta);
      console.log(meta[0].y);
    }
    setValueYPosList(heightValuesArray);
    setWasDataUpdated(!wasDataUpdated);
    // console.log("executeAfterRander");
  };
  // console.log(valueYPosList);
  useEffect(() => {
    executeAfterRender();
  }, [data]);

  useEffect(() => {
    if (wasDataUpdated) {

      executeAfterRender();
    }
  }, [wasDataUpdated]); 

  // console.log(valueYPosList);
  return (
    <div className="relative h-full w-full">
      <div className="relative">
        <Chart ref={chartRef} type="bar" options={options} data={data} />

        {data["labels"].map((month, index) => {

          const width = elDifference;
          const leftPosition =
            index === 0
              ? nothingFirstElDifference
              : nothingFirstElDifference + elDifference * index;

          return (
            <div key={index}>
              <input
                type="text"
                value={month}
                className="text-xs absolute text-center"
                style={{
                  left: `${leftPosition}px`,
                  width: `${width}px`,
                }}
              />
            </div>
          );
        })}
        {valueYPosList.map((yPos, index) => {
          const width = elDifference;
          const leftPosition =
            index === 0
              ? nothingFirstElDifference
              : nothingFirstElDifference + elDifference * index;
          return (
            <input
              key={`value-${index}`}
              className="text-xs absolute text-center mb-1 bg-transparent"
              value={data["datasets"][0].data[index]}
              onChange={(e) => {
                const newValue = e.target.value;
                changeFirstValue(newValue, index);
              }}
              style={{
                bottom: `${yPos}px`,
                left: `${leftPosition}px`, 
                width: `${width}px`, 
              }}
            />
          );
        })}
      </div>
    </div>
  );
};

export default ChartRender;

0

There are 0 answers