Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Maximum call stack error when attempting to update Chart in Vue js

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:

  1. Chart data is passed from parent to child though props. shouldUpdate prop is false by default.
  2. The chart is drawn when child component is mounted using the 'createChart' method, and data from parent
  3. A Watcher on child watches the 'shouldUpdate' prop change.
  4. On a button click inside the parent component, 'shouldUpdate' is set to True, hence triggering the watcher on the child component.
  5. The child changes chart data, updated the chart, and resets the 'shouldUpdate' prop back to False.

Expected result:

  1. Chart is rendered with data from parent component.
  2. Chart is responsive/animated as per options passed from parent
  3. Chart updated when trigger received from parent

Actual result:

  1. Chart is rendered with data from parent component.
  2. Chart is responsive/animated as per options passed from parent
  3. Chart update fails with "Maximum call stack size exceeded" error.

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.

like image 768
Petru Tanas Avatar asked Mar 20 '26 18:03

Petru Tanas


2 Answers

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,
        })
      );
    },
  },


like image 142
LeeLenalee Avatar answered Mar 23 '26 06:03

LeeLenalee


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,
        };
    },
....
}
like image 31
Petru Tanas Avatar answered Mar 23 '26 08:03

Petru Tanas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!