ChartJs typescript中的图表:对象在useEffect中可能未定义



我正在尝试使用useEffect挂钩更新chartjs。

然而,我的反应页面崩溃了,说:

TypeError:无法读取未定义的属性"data">

这是代码:

const MyChart = ({ chartData }: Props) => {
const barChartData: Chart.ChartData = {
labels: ["a", "b", "c", "d", "e", "f", "g", "h"],
datasets: [
{data: chartData},
],
};
const canvasRef = useRef<HTMLCanvasElement>(null);
var myChart: Chart;
useEffect(() => {
const ctx = canvasRef.current?.getContext("2d");
if (ctx) {
var myChart = new Chart(ctx, {
type: "radar",
data: barChartData,
options: { responsive: true },
});
}
}, []);
useEffect(() => {
if (chartData != [0, 0, 0, 0, 0, 0, 0, 0]){

const barChartDataUpdated: Chart.ChartData = {
labels: ["a", "b", "c", "d", "e", "f", "g", "h"],
datasets: [ { data: chartData } ],
};
myChart.data = barChartDataUpdated;
myChart.update();
}
}, [chartData]);
return (
<div className="self-center w-1/2">
<div className="overflow-hidden">
<canvas ref={canvasRef}></canvas>
</div>
</div>
);

据我所知,第二个useEffect最终会在Chart实际实例化之前被触发。

我试图修改代码,将更新放在与创建myChart相同的useEffect中,它很有效,但每次创建chartData时,都会在上一个图表的基础上创建一个新的图表,这很有缺陷。

useEffect(() => {
const ctx = canvasRef.current?.getContext("2d");
if (ctx) {
var myChart = new Chart(ctx, {
type: "radar",
data: barChartData,
options: { responsive: true }
});
myChart.data.datasets = [ { data: chartData } ]
myChart.update();
}
}, [chartData]);

我也尝试过在useEffect之外创建对象,但根本不起作用。

更新chartData时,更新myChart的正确方法是什么?

我的建议是使用回调引用,因为您正在处理需要元素才能实例化的第三方库。

回调ref是一个将元素作为参数的函数。我们创建了一个用canvas调用的函数canvasCallback,并用它来创建图表实例,我通过useRef而不是useState存储它,因为它是可变的(尽管我认为这并不重要,因为所有重要的重新渲染都是由chart.js而不是React完成的(。

我们还需要一个useEffect钩子来检测来自props的数据的变化。由于您在这里以与以前相同的方式创建Chart.ChartData对象,因此我将该逻辑移动到一个辅助函数formatData

组件

import Chart from "chart.js";
import { useRef, useEffect, useState } from "react";
interface Props {
chartData: number[];
}
const MyChart = ({ chartData }: Props) => {
// helper function to format chart data since you do this twice
const formatData = (data: number[]): Chart.ChartData => ({
labels: ["a", "b", "c", "d", "e", "f", "g", "h"],
datasets: [{ data }]
});
// use a ref to store the chart instance since it it mutable
const chartRef = useRef<Chart | null>(null);
// callback creates the chart on the canvas element
const canvasCallback = (canvas: HTMLCanvasElement | null) => {
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (ctx) {
chartRef.current = new Chart(ctx, {
type: "radar",
data: formatData(chartData),
options: { responsive: true }
});
}
};
// effect to update the chart when props are updated
useEffect(() => {
// must verify that the chart exists
const chart = chartRef.current;
if (chart) {
chart.data = formatData(chartData);
chart.update();
}
}, [chartData]);
return (
<div className="self-center w-1/2">
<div className="overflow-hidden">
<canvas ref={canvasCallback}></canvas>
</div>
</div>
);
};

模拟测试仪

export default () => {
const [data, setData] = useState([0, 1, 2, 3, 4, 5, 6, 7]);
// want to see some changes in the props on order to make sure that MyChart updates
const onClick = () => {
setData((prevData) => prevData.slice(1).concat(10 * Math.random()));
};
return (
<div>
<button onClick={onClick}>Change</button>
<MyChart chartData={data} />
</div>
);
};

代码沙盒链接

这是因为当您的chartData未定义时,第二个useEffect也会触发。您的if(chartData!=[0,0,0,0,0,0,0](始终为true,因为您比较的是引用,而不是基元值。。您可以尝试如果(chartData&&chartData.data(检查它们是否为未定义的

最新更新