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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions