devstark-com / vue-google-charts

Reactive Vue.js wrapper for Google Charts lib
446 stars 74 forks source link

Vue lifecycle interfering with Google Charts? #27

Open chimmelb opened 5 years ago

chimmelb commented 5 years ago

We have been getting this error recently using vue-google-charts. The error is in Google Charts, but likely due to how we are using everything. Our use case is a dashboard with a few charts - changing certain control will re-query our API and get new data for the charts.

Here is one error:

[Vue warn]: Error in beforeDestroy hook: "TypeError: Cannot read property 'clear' of null"
found in
---> <GChart>

   vue.esm.js?efeb:1741 TypeError: Cannot read property 'clear' of null
    at gvjs_VL (jsapi_compiled_ui_module.js:1016)
    at gvjs_8L.gvjs_.Ef (jsapi_compiled_ui_module.js:1016)
    at gvjs_8L.gvjs_.Wb (jsapi_compiled_default_module.js:240)
    at VueComponent.beforeDestroy (vue-google-charts.common.js?2973:1)
    at callHook (vue.esm.js?efeb:2921)
    at VueComponent.Vue.$destroy (vue.esm.js?efeb:2700)
    at destroy (vue.esm.js?efeb:4178)
    at invokeDestroyHook (vue.esm.js?efeb:5743)
    at VueComponent.patch [as __patch__] (vue.esm.js?efeb:6081)
    at VueComponent.Vue.$destroy (vue.esm.js?efeb:2723)

That appears often, and sometimes renders a red bar with that TypeError message on the google chart itself. That's really the worst case because our dashboard looks broken. Here's an image of our component, the red bar is Google Charts, not us.

image

Here is another. This happens only in the console, rendering looks fine. Sorry for the long trace:

jsapi_compiled_default_module.js:151 Uncaught (in promise) Error: Container is not defined
    at gvjs_cp (jsapi_compiled_default_module.js:151)
    at gvjs_UL.gvjs_Sq [as constructor] (jsapi_compiled_default_module.js:239)
    at gvjs_UL.gvjs_TL [as constructor] (jsapi_compiled_ui_module.js:998)
    at new gvjs_UL (jsapi_compiled_ui_module.js:1029)
    at createChart (vue-google-charts.common.js?2973:1)
    at VueComponent.createChartObject (vue-google-charts.common.js?2973:1)
    at eval (vue-google-charts.common.js?2973:1)

gvjs_cp | @ | jsapi_compiled_default_module.js:151
-- | -- | --
  | gvjs_Sq | @ | jsapi_compiled_default_module.js:239
  | gvjs_TL | @ | jsapi_compiled_ui_module.js:998
  | gvjs_UL | @ | jsapi_compiled_ui_module.js:1029
  | createChart | @ | vue-google-charts.common.js?2973:1
  | createChartObject | @ | vue-google-charts.common.js?2973:1
  | (anonymous) | @ | vue-google-charts.common.js?2973:1
  | Promise.then (async) |   |  
  | mounted | @ | vue-google-charts.common.js?2973:1
  | callHook | @ | vue.esm.js?efeb:2921
  | insert | @ | vue.esm.js?efeb:4158
  | invokeInsertHook | @ | vue.esm.js?efeb:5960
  | patch | @ | vue.esm.js?efeb:6179
  | Vue._update | @ | vue.esm.js?efeb:2670
  | updateComponent | @ | vue.esm.js?efeb:2788
  | get | @ | vue.esm.js?efeb:3142
  | run | @ | vue.esm.js?efeb:3219
  | flushSchedulerQueue | @ | vue.esm.js?efeb:2981
  | (anonymous) | @ | vue.esm.js?efeb:1837
  | flushCallbacks | @ | vue.esm.js?efeb:1758
  | Promise.then (async) |   |  
  | microTimerFunc | @ | vue.esm.js?efeb:1806
  | nextTick | @ | vue.esm.js?efeb:1850
  | queueWatcher | @ | vue.esm.js?efeb:3068
  | update | @ | vue.esm.js?efeb:3209
  | notify | @ | vue.esm.js?efeb:697
  | reactiveSetter | @ | vue.esm.js?efeb:1014
  | proxySetter | @ | vue.esm.js?efeb:3300
  | (anonymous) | @ | DemoDashboard.vue?7e47:302
  | Promise.then (async) |   |  
  | _callee$ | @ | DemoDashboard.vue?7e47:291
  | tryCatch | @ | runtime.js?4a57:62
  | invoke | @ | runtime.js?4a57:296
  | prototype.(anonymous function) | @ | runtime.js?4a57:114
  | step | @ | asyncToGenerator.js?7b11:17
  | (anonymous) | @ | asyncToGenerator.js?7b11:28
  | Promise.then (async) |   |  
  | step | @ | asyncToGenerator.js?7b11:27
  | (anonymous) | @ | asyncToGenerator.js?7b11:35
  | F | @ | _export.js?90cd:35
  | (anonymous) | @ | asyncToGenerator.js?7b11:14
  | getData | @ | DemoDashboard.vue?7e47:322
  | updateFilter | @ | DemoDashboard.vue?7e47:244
  | updateFilter | @ | DemoDashboard.vue?eab5:908
  | invoker | @ | vue.esm.js?efeb:2027
  | Vue.$emit | @ | vue.esm.js?efeb:2538
  | refresh | @ | FilterBar.vue?7e53:218
  | refresh | @ | FilterBar.vue?2703:332
  | invoker | @ | vue.esm.js?efeb:2027
  | Vue.$emit | @ | vue.esm.js?efeb:2538
  | refresh | @ | DateFilter.vue?f8b2:69
  | startDate | @ | DateFilter.vue?f8b2:83
  | run | @ | vue.esm.js?efeb:3233
  | flushSchedulerQueue | @ | vue.esm.js?efeb:2981
  | (anonymous) | @ | vue.esm.js?efeb:1837
  | flushCallbacks | @ | vue.esm.js?efeb:1758
  | Promise.then (async) |   |  
  | microTimerFunc | @ | vue.esm.js?efeb:1806
  | nextTick | @ | vue.esm.js?efeb:1850
  | queueWatcher | @ | vue.esm.js?efeb:3068
  | update | @ | vue.esm.js?efeb:3209
  | notify | @ | vue.esm.js?efeb:697
  | reactiveSetter | @ | vue.esm.js?efeb:1014
  | setFilterLists | @ | filters.js?cc05:136
  | wrappedMutationHandler | @ | vuex.esm.js?358c:697
  | commitIterator | @ | vuex.esm.js?358c:389
  | (anonymous) | @ | vuex.esm.js?358c:388
  | _withCommit | @ | vuex.esm.js?358c:495
  | commit | @ | vuex.esm.js?358c:387
  | boundCommit | @ | vuex.esm.js?358c:335
  | (anonymous) | @ | filters.js?cc05:105
  | Promise.then (async) |   |  
  | loadInspFilters | @ | filters.js?cc05:104
  | wrappedActionHandler | @ | vuex.esm.js?358c:704
  | dispatch | @ | vuex.esm.js?358c:426
  | boundDispatch | @ | vuex.esm.js?358c:332
  | loadFilters | @ | FilterBar.vue?7e53:199
  | mounted | @ | FilterBar.vue?7e53:164
  | callHook | @ | vue.esm.js?efeb:2921
  | insert | @ | vue.esm.js?efeb:4158
  | invokeInsertHook | @ | vue.esm.js?efeb:5960
  | patch | @ | vue.esm.js?efeb:6179
  | Vue._update | @ | vue.esm.js?efeb:2670
  | updateComponent | @ | vue.esm.js?efeb:2788
  | get | @ | vue.esm.js?efeb:3142
  | run | @ | vue.esm.js?efeb:3219
  | flushSchedulerQueue | @ | vue.esm.js?efeb:2981
  | (anonymous) | @ | vue.esm.js?efeb:1837
  | flushCallbacks | @ | vue.esm.js?efeb:1758
  | Promise.then (async) |   |  
  | microTimerFunc | @ | vue.esm.js?efeb:1806
  | nextTick | @ | vue.esm.js?efeb:1850
  | queueWatcher | @ | vue.esm.js?efeb:3068
  | update | @ | vue.esm.js?efeb:3209
  | notify | @ | vue.esm.js?efeb:697
  | reactiveSetter | @ | vue.esm.js?efeb:1014
  | (anonymous) | @ | vue-router.esm.js?fe87:2508
  | (anonymous) | @ | vue-router.esm.js?fe87:2507
  | updateRoute | @ | vue-router.esm.js?fe87:1997
  | (anonymous) | @ | vue-router.esm.js?fe87:1875
  | (anonymous) | @ | vue-router.esm.js?fe87:1984
  | step | @ | vue-router.esm.js?fe87:1714
  | step | @ | vue-router.esm.js?fe87:1721
  | step | @ | vue-router.esm.js?fe87:1721
  | runQueue | @ | vue-router.esm.js?fe87:1725
  | (anonymous) | @ | vue-router.esm.js?fe87:1979
  | step | @ | vue-router.esm.js?fe87:1714
  | (anonymous) | @ | vue-router.esm.js?fe87:1718
  | (anonymous) | @ | vue-router.esm.js?fe87:1964
  | (anonymous) | @ | vue-router.esm.js?fe87:1757
  | (anonymous) | @ | vue-router.esm.js?fe87:1833
  | Promise.then (async) |   |  
  | (anonymous) | @ | vue-router.esm.js?fe87:1780
  | (anonymous) | @ | vue-router.esm.js?fe87:1801
  | (anonymous) | @ | vue-router.esm.js?fe87:1801
  | flatMapComponents | @ | vue-router.esm.js?fe87:1800
  | (anonymous) | @ | vue-router.esm.js?fe87:1736
  | iterator | @ | vue-router.esm.js?fe87:1943
  | step | @ | vue-router.esm.js?fe87:1717
  | step | @ | vue-router.esm.js?fe87:1721
  | step | @ | vue-router.esm.js?fe87:1721
  | (anonymous) | @ | vue-router.esm.js?fe87:1718
  | (anonymous) | @ | vue-router.esm.js?fe87:1964
  | _callee$ | @ | routes.js?7aab:26
  | tryCatch | @ | runtime.js?4a57:62
  | invoke | @ | runtime.js?4a57:296
  | prototype.(anonymous function) | @ | runtime.js?4a57:114
  | step | @ | asyncToGenerator.js?7b11:17
  | (anonymous) | @ | asyncToGenerator.js?7b11:28
  | Promise.then (async) |   |  
  | step | @ | asyncToGenerator.js?7b11:27
  | (anonymous) | @ | asyncToGenerator.js?7b11:35
  | F | @ | _export.js?90cd:35
  | (anonymous) | @ | asyncToGenerator.js?7b11:14
  | checkNavigationGuards | @ | routes.js?7aab:14
  | _callee$ | @ | main.js?3479:67
  | tryCatch | @ | runtime.js?4a57:62
  | invoke | @ | runtime.js?4a57:296
  | prototype.(anonymous function) | @ | runtime.js?4a57:114
  | step | @ | asyncToGenerator.js?7b11:17
  | (anonymous) | @ | asyncToGenerator.js?7b11:35
  | F | @ | _export.js?90cd:35
  | (anonymous) | @ | asyncToGenerator.js?7b11:14
  | (anonymous) | @ | main.js?3479:64
  | iterator | @ | vue-router.esm.js?fe87:1943
  | step | @ | vue-router.esm.js?fe87:1717
  | runQueue | @ | vue-router.esm.js?fe87:1725
  | confirmTransition | @ | vue-router.esm.js?fe87:1972
  | transitionTo | @ | vue-router.esm.js?fe87:1874
  | init | @ | vue-router.esm.js?fe87:2499
  | beforeCreate | @ | vue-router.esm.js?fe87:540
  | callHook | @ | vue.esm.js?efeb:2921
  | Vue._init | @ | vue.esm.js?efeb:4626
  | Vue | @ | vue.esm.js?efeb:4729
  | (anonymous) | @ | main.js?3479:98
  | (anonymous) | @ | 192:180
  | (anonymous) | @ | app.js:2027
  | __webpack_require__ | @ | app.js:708
  | fn | @ | app.js:113
  | (anonymous) | @ | app.js:4348
  | __webpack_require__ | @ | app.js:708
  | (anonymous) | @ | app.js:806
  | (anonymous) | @ | app.js:809

I think both are related to vue-google-charts creating a Google Chart chartObject without data. So when it tries to render or is destroyed, there are errors since something wasn't initialized correctly. But due to Vue's component lifecycle, some times (most times!) these errors are just buried in the console and the charts render fine.

I've tried adding changing keys to our Vue components - in case some component had trouble re-using the Chart data/object/component.

Has anyone encountered this before? Have a way to build/render your charts that prevents the error?

chimmelb commented 5 years ago

I also will add that sometimes the Google Charts red error box (as in the picture) can occur without a console error.

In one case we have a few data sets pre-loaded on the page and without a new API call can set the chart data. The red bar is present on the first or second render, then works with a 3rd option, then works on the first and second options again.

This is an odd timing issue and I'm looking for ways to avoid it. Just something about the binding of Vue data through vue-google-charts - it works most of the time but in our setup these errors are somewhat repeatable (not boiled down into a JS Fiddle or anything yet...)

One more piece of info. We have <GChart v-if="values" ...> in our component, and I added the values above the chart just to display them. Getting this:

image

That kind of confirms that the data is there and ready, but the chart already died. If I change some options on my page and then come back to that data, i.e. it gets re-bound through Vue props, the chart works again.

tunkul commented 4 years ago

I've also seen random behavior. Things work fine, but sometimes i end up with a blank chart (no error message).

Looking at some code:

if (data) this.chartObject.draw(data, this.options)

I think this can be robust'ified. Eg, when GChart is used within one's own component, the data & options var might initially be null/empty while a server call is made to fetch the data. Eg, they could be null, might be set to an empty object {}, might be set to an empty array [], So add some more checks for "good" data & options variables before drawing.