I have a line chart drawn using Chart.js 3.5 library. the Chart is rendered correctly, is responsive, and all the animations work.
I am trying to trigger a data update from a parent component, and trigger a chart update. When chart.update() is called, an exception is thrown: "Uncaught (in promise) RangeError: Maximum call stack size exceeded"
I am using Vue 3 and Chart.js 3.5
Edit1: Here is a sandbox to replicate the error.
The logic is as follows:
Expected result:
Actual result:
Parent element: Line.vue
<template>
<raw
:type="type"
:options="options"
:data="chartData"
:shouldUpdate="shouldUpdate"
:resetUpdate="resetUpdate"
/>
<button @click="updateData"></button>
</template>
<script>
import Raw from "./Raw.vue";
export default {
name: "App",
components: {
Raw,
},
computed: {
chartData: function () {
return this.data;
},
},
methods: {
updateData() {
this.shouldUpdate = true;
},
resetUpdate() {
this.shouldUpdate = false;
},
},
data() {
return {
type: "line",
shouldUpdate: false,
options: {
responsive: true,
animation: true,
maintainAspectRatio: false,
},
data: {
labels: ["Jan", "Feb", "Mar", "Apr", "May", "June", "July"],
datasets: [
{
label: "My line Dataset",
data: [65, 59, 80, 81, 56, 55, 40],
fill: false,
borderColor: "rgb(75, 192, 192)",
tension: 0.1,
},
{
label: "My second line Dataset",
data: [100, 79, 8, 80, 90, 55, 60],
fill: false,
borderColor: "rgb(75, 19, 192)",
tension: 0.1,
},
],
},
};
},
};
</script>
Child component: Raw.vue
<template>
<canvas />
</template>
<script>
import Chart from "chart.js/auto";
export default {
name: "Raw",
props: ["type", "data", "options", "shouldUpdate", "resetUpdate"],
data() {
return {
chart: null,
localData: null,
};
},
computed: {
parentEl: function () {
const size = Math.min(this.$parent.width, this.$parent.height);
return { height: size, width: size };
},
},
watch: {
shouldUpdate: function (val) {
console.log(val);
if (val) {
console.log("updateTriggered");
console.log(this.chart.data.datasets[0].data[5])
this.chart.data.datasets[0].data[5] = Math.round(Math.random() * 100)
console.log(this.chart.data.datasets[0].data[5]) // check if data changed
this.chart.update(); // this seems to cause the error
}
},
},
methods: {
createChart() {
this.localData = this.data;
this.chart = new Chart(this.$el, {
type: this.type,
data: this.localData,
options: this.options,
});
},
},
mounted() {
this.createChart();
},
};
</script>
<style scoped>
</style>
Error traceback:
[Vue warn]: Unhandled error during execution of watcher callback
at <Raw type="line" options= {} data= {labels: Array(7), datasets: Array(2)} ... >
at <App>
runtime-core.esm-bundler.js?5c40:38 [Vue warn]: Unhandled error during execution of scheduler flush. This is likely a Vue internals bug. Please open an issue at https://new-issue.vuejs.org/?repo=vuejs/vue-next
at <Raw type="line" options= {} data= {labels: Array(7), datasets: Array(2)} ... >
at <App>
Uncaught (in promise) RangeError: Maximum call stack size exceeded
at _resolveWithPrefixes (helpers.segment.js?dd3d:1797)
at eval (helpers.segment.js?dd3d:1605)
at _cached (helpers.segment.js?dd3d:1687)
at Object.get (helpers.segment.js?dd3d:1604)
at _resolveWithContext (helpers.segment.js?dd3d:1695)
at eval (helpers.segment.js?dd3d:1647)
at _cached (helpers.segment.js?dd3d:1687)
at Object.get (helpers.segment.js?dd3d:1646)
at toRaw (reactivity.esm-bundler.js?a1e9:743)
at toRaw (reactivity.esm-bundler.js?a1e9:743)
sandbox to replicate the error.
adding on Petru Tanas workaround to make the chart non reactive you can also use a shallowRef
import { shallowRef } from 'vue';
data() {
return {
chart: null,
localData: null,
};
},
methods: {
createChart() {
this.localData = this.data;
this.chart = shallowRef(
new Chart(this.$el, {
type: this.type,
data: this.localData,
options: this.options,
})
);
},
},
I found a workaround which works in my particular case, but might not be ideal for everyone.
The workaround is to make the chart object non reactive, so that Vue does not track changes on it, by moving it out of the 'return' statement of component 'data'. The chart object is still available everywhere inside the component, and all features (responsive, animated, etc) work as expected, but is not tracked by Vue anywhere else, so this might cause issues if you try to pass it across components, or use it with Vuex
Code as follows:
Does not work:
<template>
<canvas />
</template>
<script>
import Chart from "chart.js/auto";
export default {
name: "Raw",
props: ["type", "data", "options", "shouldUpdate", "resetUpdate"],
data() {
return {
chart: null, // this line will change
localData: null,
};
},
....
}
Working:
<template>
<canvas />
</template>
<script>
import Chart from "chart.js/auto";
export default {
name: "Raw",
props: ["type", "data", "options", "shouldUpdate", "resetUpdate"],
data() {
this.chart = null // this line changed
return {
localData: null,
};
},
....
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With