import { createEffect, onCleanup, onMount } from "solid-js"; import { Chart, registerables } from "chart.js"; // chart.js v4 is tree-shakeable and ships nothing registered by default; register // all controllers/elements/scales once so any chart type works (the old UMD shim // did this implicitly). Chart.register(...registerables); type ChartType = "line" | "bar" | "radar" | "doughnut" | "polarArea" | "bubble" | "pie" | "scatter"; interface ReactiveChartProps { type: ChartType; data: unknown; options?: object; class?: string; } interface ChartInstance { destroy(): void; update(): void; data: unknown; options: object; } type ChartCtor = new (ctx: CanvasRenderingContext2D, cfg: object) => ChartInstance; export default function ReactiveChart(props: ReactiveChartProps) { let canvasRef: HTMLCanvasElement | undefined; let chartInstance: ChartInstance | null = null; onMount(() => { // Defer initialization until the canvas is connected to the document. // @solidjs/router creates route components before inserting them into // the DOM, and Chart.js needs getComputedStyle which requires a // connected element with ownerDocument.defaultView. const init = () => { if (!canvasRef) return; if (!canvasRef.isConnected) { requestAnimationFrame(init); return; } const ctx = canvasRef.getContext("2d"); if (!ctx) return; chartInstance = new (Chart as unknown as ChartCtor)(ctx, { type: props.type, data: props.data, options: { responsive: true, maintainAspectRatio: false, ...(props.options ?? {}), }, }); }; init(); }); onCleanup(() => { if (chartInstance) { chartInstance.destroy(); chartInstance = null; } }); createEffect(() => { const data = props.data; if (chartInstance) { chartInstance.data = data; chartInstance.update(); } }); createEffect(() => { const options = props.options; if (chartInstance) { chartInstance.options = { responsive: true, maintainAspectRatio: false, ...(options ?? {}), }; chartInstance.update(); } }); return (
canvasRef = el}>
); }