-
Notifications
You must be signed in to change notification settings - Fork 1
Description
I see 2 charts per chart, seems to be in the lib according to claude.
fully AI below here. I've not looked into it.
Root Cause: Two Canvas Elements per Chart (ChartGPU)
The duplicate <canvas> issue comes from how chartgpu-react and @chartgpu/chartgpu interact under React StrictMode.
How the DOM gets created
Layer 1: chartgpu-react renders a <div> container
File: node_modules/chartgpu-react/dist/index.js
Lines: ~206–213
/* @__PURE__ */ L(
"div", // <-- renders a <div>, stored in ref `x`
{
ref: x,
className: c,
style: s
}
);Layer 2: ChartGPU.create() appends a <canvas> into that <div>
File: node_modules/@chartgpu/chartgpu/dist/index.js
Lines: ~9009–9010
const i = document.createElement("canvas");
i.style.display = "block",
i.style.width = "100%",
i.style.height = "100%",
e.appendChild(i);Layer 3: the React component calls ChartGPU.create(x.current, ...)
File: node_modules/chartgpu-react/dist/index.js
Lines: ~89–97
return R(() => { // useEffect
if (!x.current) return;
E.current = !0;
let e = null;
return (async () => {
// ...
e = w ? await P.create(x.current, p, w) : await P.create(x.current, p);At this point, everything is fine: one <div> containing one <canvas>.
The actual bug: React StrictMode double-mount
In development with React StrictMode, effects run:
mount → cleanup → remount
The useEffect cleanup disposes the chart instance, but does not remove the canvas DOM node:
File: node_modules/chartgpu-react/dist/index.js
Lines: ~101–103
() => {
E.current = !1,
C.current && !C.current.disposed && (C.current.dispose(), C.current = null),
d(null);
};And ChartGPUInstance.dispose() does not remove the <canvas> it appended—it only releases GPU/RAF resources.
StrictMode sequence
- First mount:
create(div)appends canvas Add a modern React wrapper around ChartGPU with full lifecycle management, typed events, and improved example performance #1 - Cleanup:
dispose()runs, but canvas Add a modern React wrapper around ChartGPU with full lifecycle management, typed events, and improved example performance #1 stays in the DOM - Second mount:
create(div)appends canvas Refactor LineChartExample for simpler line series configuration #2
Result: two <canvas> elements inside the same container <div>.
Exact code causing the bug (as shipped)
File: node_modules/chartgpu-react/dist/index.js (lines ~89–103)
return R(() => {
if (!x.current) return;
E.current = !0;
let e = null;
return (async () => {
try {
if (!x.current) return;
const p = k(), w = g.current;
e = w ? await P.create(x.current, p, w) : await P.create(x.current, p);
E.current ? (C.current = e, d(e), f == null || f(e)) : e.dispose();
} catch (p) {
E.current && console.error("Failed to create ChartGPU instance:", p);
}
})(), () => {
E.current = !1;
C.current && !C.current.disposed && (C.current.dispose(), C.current = null);
d(null);
// Missing: removing the child canvas element(s)
};
}, []);Fix
The cleanup must remove any DOM children that ChartGPU.create() appended:
() => {
E.current = !1;
C.current && !C.current.disposed && (C.current.dispose(), C.current = null);
d(null);
// FIX: Remove the canvas element(s) appended by ChartGPU.create()
if (x.current) {
while (x.current.firstChild) {
x.current.removeChild(x.current.firstChild);
}
}
};Conclusion
This is a bug in chartgpu-react v0.1.3, not in the dashboard adapter. The adapter (/<redacted>/src/charts/adapters/chartgpu-adapter.jsx) is using <ChartGPU> correctly.