4π
My recommendation is to use callback refs for you are dealing with third-party libraries that require an element in order to instantiate.
The callback ref is a function which takes the element as an argument. We create a function canvasCallback
that gets called with the canvas
and we use that to create the chart instance, which I am storing via useRef
rather than useState
since it is mutable (though I donβt think it really matters since all of the important re-rendering is done by chart.js
rather than React).
We also need a useEffect
hook to detect changes in the data from props. Since you are creating the Chart.ChartData
object the same way here as before, I moved that logic into a helper function formatData
Component
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>
);
};
Dummy tester
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>
);
};
0π
It is because the second useEffect also triggers when your chartData is undefined. Your if(chartData != [0, 0, 0, 0, 0, 0, 0, 0]) is always true because you are comparing references, not primitive values.. You can try if(chartData && chartData.data) to check they are not undefined