chartjs/Chart.js · critical · Error

Canvas is already in use. Chart with ID '${existingChart.id}

Error message

Canvas is already in use. Chart with ID '${existingChart.id}' must be destroyed before the canvas with ID '${existingChart.canvas.id}' can be reused.

What it means

The Chart constructor calls getCanvas(item) then getChart(initialCanvas); the latter looks up a registry of live Chart instances keyed by canvas. If a Chart already exists on that canvas, the constructor refuses to create a second one and throws, naming the existing chart's id and canvas id. This guards against double-initialization which would leak listeners, corrupt the platform context, and double-render.

Source

Thrown at src/core/core.controller.js:128

  static getChart = getChart;

  static register(...items) {
    registry.add(...items);
    invalidatePlugins();
  }

  static unregister(...items) {
    registry.remove(...items);
    invalidatePlugins();
  }

  // eslint-disable-next-line max-statements
  constructor(item, userConfig) {
    const config = this.config = new Config(userConfig);
    const initialCanvas = getCanvas(item);
    const existingChart = getChart(initialCanvas);
    if (existingChart) {
      throw new Error(
        'Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' +
				' must be destroyed before the canvas with ID \'' + existingChart.canvas.id + '\' can be reused.'
      );
    }

    const options = config.createResolver(config.chartOptionScopes(), this.getContext());

    this.platform = new (config.platform || _detectPlatform(initialCanvas))();
    this.platform.updateConfig(config);

    const context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
    const canvas = context && context.canvas;
    const height = canvas && canvas.height;
    const width = canvas && canvas.width;

    this.id = uid();
    this.ctx = context;
    this.canvas = canvas;

View on GitHub (pinned to cb02e1d207)

Solutions

  1. Destroy the existing chart before recreating: keep a ref and call existingChart.destroy() in your cleanup/effect-return before `new Chart`.
  2. Reuse the instance: call chart.update() / chart.data = ...; chart.update() instead of new Chart() when only data changes.
  3. Use Chart.getChart(canvas) to fetch the live instance and update it rather than constructing a new one.
  4. In framework lifecycle hooks, ensure the destroy runs on unmount (e.g. useEffect(() => { const c = new Chart(...); return () => c.destroy(); }, [])).

Example fix

// before
function render() {
  new Chart(canvas, config); // re-runs on every render -> throws
}

// after
let chart;
function render() {
  if (chart) { chart.destroy(); chart = null; }
  chart = new Chart(canvas, config);
}
Defensive patterns

Strategy: validation

Validate before calling

// Ensure the canvas is free (or reuse the existing instance) before constructing.
import { Chart } from 'chart.js';

function getOrCreateChart(canvas, config) {
  const existing = Chart.getChart(canvas);
  if (existing) {
    // Option A: reuse and update; Option B: destroy then recreate.
    existing.data = config.data;
    existing.options = config.options;
    existing.update();
    return existing;
  }
  return new Chart(canvas, config);
}

Prevention

When it happens

Trigger: Calling `new Chart(canvas, ...)` twice on the same canvas/2d-context/element without calling `.destroy()` on the first; HMR / React/VE component re-render that creates a Chart in an effect without a cleanup destroy; SPAs navigating away and back while the prior Chart instance was never disposed.

Common situations: React/Vue/Svelte lifecycle where the effect/fn re-runs (Strict Mode double-invoke in React 18 dev, dev hot-reload); reusing a canvas element reference held outside the chart; rebuilding a dashboard widget on data change by re-instantiating instead of calling chart.update().


AI-assisted analysis of chartjs/Chart.js@cb02e1d207 (2026-08-12). Data as JSON: /api/errors/9e7a6653f1739ad4. Report an issue: GitHub.