Search code examples
javascriptvue.jsplotly

Plotly.js renders wrong chart when used within a deleted Vue.js component


Problem

I got two charts wrapped inside two components. When I delete the first component, the first chart stays.

See jsfiddle: https://jsfiddle.net/m4ywp5fc/4/

Approach

I'm trying to replot the chart using Plotly.replot but it seems that Vue.js does something internally which I don't understand. Also Plotly.purge does nothing in this case.

Code

Or see jsfiddle: https://jsfiddle.net/m4ywp5fc/4/

JavaScript

Vue.component('chart-one', {
    props: ['id'],

  template: `<div :id="'chart-' + id"></div>`,

  mounted () {
    const trace = {
      x: [1, 2, 3, 4],
      y: Array.from({length: 4}, () => Math.floor(Math.random() * 40))
    }

    Plotly.newPlot('chart-' + this.id, [trace])
  }
})

new Vue({
  el: '#app',

  data: () => ({
    charts: [1, 2]
  }),

  methods: {
    remove () {
        this.charts.splice(0, 1)
    }
  }
})

HTML

<script src="https://unpkg.com/vue"></script>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>

<div id="app">
  Charts: {{ charts }}
  <button @click="remove">
    Delete first chart
  </button>

  <div v-for="(chart, index) in charts">
    <chart-one :id="'chart-' + index"></chart-one>
  </div>
</div>

Additional note

Please notice that my application is much more complex.


Solution

  • Your problem is caused by 2 reasons:

    A typo in your demo code

    <chart-one :id="'chart-' + index"></chart-one>
    

    should be

    <chart-one :id="'chart-' + chart"></chart-one>
    

    Failure to handle changing of props inside your component

    Vue, by default assumes that a component automatically updates when its properties are changed, while this is part of the framework itself, your component fails to handle this situation.

    This is what happens:

    1. Vue first renders with 2 instances of chart-one, and the values [1, 2]
    2. When you click the button, Vue is asked to re-render with 1 chart-one [1]
    3. Vue updates the id variable on the first component to 2, and then sees it still has 1 unused component remaining, so it nukes it

    This can be solved in the following ways:

    Adding a key variable:

    If you add a key to the chart-one component, vue can actually do "tracking" of the instances:

    <chart-one :id="'chart-' + chart" :key="chart"></chart-one>
    

    Instead of doing the "update values in the first component" it now intelligently nukes and recreates dom nodes to match the values passed in.