Skip to content

Issue where two charts show in development #15

@dogmatic69

Description

@dogmatic69

I see 2 charts per chart, seems to be in the lib according to claude.

Image

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

  1. First mount: create(div) appends canvas Add a modern React wrapper around ChartGPU with full lifecycle management, typed events, and improved example performance #1
  2. 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
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions