highcharts / highcharts-vue

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TypeError: Cannot read properties of undefined (reading 'update')

nevermin9 opened this issue · comments

commented

We are using Vue 3 on our project.
I was really glad, that this package completely compatible with Vue 3.
But I faced with strange issue: looks like watch of the Chart component runs before mounted
I cloned your repo, set console.log and found out this.
Maybe someone faced with the same behavior, bc it is really, really strange thing.
Error

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'update')
    at Proxy.handler (highcharts-vue.min.js?3da3:1:1)
    at callWithErrorHandling (runtime-core.esm-bundler.js?d2dd:155:1)
    at callWithAsyncErrorHandling (runtime-core.esm-bundler.js?d2dd:164:1)
    at Array.job (runtime-core.esm-bundler.js?d2dd:1782:1)
    at flushPreFlushCbs (runtime-core.esm-bundler.js?d2dd:328:1)
    at updateComponentPreRender (runtime-core.esm-bundler.js?d2dd:5720:1)
    at ReactiveEffect.componentUpdateFn [as fn] (runtime-core.esm-bundler.js?d2dd:5633:1)
    at ReactiveEffect.run (reactivity.esm-bundler.js?89dc:185:1)
    at instance.update (runtime-core.esm-bundler.js?d2dd:5694:1)
    at updateComponent (runtime-core.esm-bundler.js?d2dd:5519:1)

We use it like

<!-- Chart.vue - our custom component -->
<template>
  <div class="relative">
    <highcharts :options="chartOptions" />
    <div
      v-if="isLoading"
    >
      <loading-indicator />
    </div>
  </div>
</template>

...and use it in the table

// ui-table is responsible for show/hide content according to isLoading state
//  <template #cell(table-cell)="{item}"> is not visible while isLoading = true
  <ui-table :is-loading="isLoading">
<!-- other staff -->
    <template #cell(table-cell)="{item}">
      <ui-link :to="{}">
        <chart
          :is-loading="false"
          :options="chartOptions"
        />
      </ui-link>
    </template>
</ui-table>

I have been working with Vue more than 3 years and after spending several days to find out what the hell is happening, I am afraid, I have to change my favorite framework...or a profession

commented

looks like adding flush: "post" resolve this issue
https://vuejs.org/guide/essentials/watchers.html#callback-flush-timing

Experiencing the same issue, but using the flush: post option just make it happens on initial load as well.

Hi @nevermin9,

Thank you for contacting us, and reporting the issue. Unfortunately, I can't reproduce the issue you described above, and can't get any error while updating the chart. Would you be able to provide me with some minimal example (or even repository) where the problem is noticeable? That would really make this case much easier to solve.

Kind regards!

commented

Hi @Denyllon ,
sorry, but I cannot share code of company's project
even if I could, it wouldn't help you.
Because this error is thrown only in production. I can share you our vue.config.js if it helps

const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const webpack = require("webpack");
const TerserPlugin = require("terser-webpack-plugin");
const MomentTimezoneDataPlugin = require('moment-timezone-data-webpack-plugin');
const currentYear = new Date().getFullYear();

const shouldGenerateReport = process.env.GENERATE_REPORT === "true";
const isDev = process.env.NODE_ENV !== "production";

module.exports = {
  chainWebpack: config => {
    config.module
      .rule("graphql")
      .test(/\.(graphql|gql)$/)
      .use("graphql-tag/loader")
      .loader("graphql-tag/loader")
      .end();
  },

	configureWebpack: {
    cache: isDev ? { type: 'filesystem' } : false,

    devtool: isDev ? "eval-cheap-source-map" : false,

    resolve: {
      fallback: {
        fs: false
      }
    },

    optimization: {
      realContentHash: false,
      moduleIds: "deterministic",
      usedExports: true,
      runtimeChunk: {
        name: "runtime",
      },

      minimize: !isDev,

      minimizer: [
        new TerserPlugin({
          parallel: true,
          extractComments: "all",
        }),
      ],

      splitChunks: {
        chunks: 'all',
        maxInitialRequests: Infinity,
        cacheGroups: {
          common: {
            name: "common-components",
            test: /[\\/]src[\\/]components[\\/]/,
            enforce: true
          },
          vendors: {
            name: "vendor",
            test: /[\\/]node_modules[\\/]/,
            priority: -10
          }
        },
      },
    },

    devServer: {
      client: {
        logging: "log",
        progress: false,
      },
    },

		plugins: [
      new MomentTimezoneDataPlugin({
        matchZones: /(UTC|utc)/,
        startYear: currentYear - 5,
        endYear: currentYear + 5,
      }),

			new NodePolyfillPlugin(),

      new BundleAnalyzerPlugin({
        analyzerPort: "3000",
        analyzerMode: shouldGenerateReport ? "server" : "disabled",
      }),

      new webpack.IgnorePlugin({
        resourceRegExp: /^\.\/locale$/,
        contextRegExp: /moment$/,
      })
		]
	}
}

Just in case it may help someone else. We solved this by checking that the options were set before rendering the chart with a simple v-if clause.

commented

Just in case it may help someone else. We solved this by checking that the options were set before rendering the chart with a simple v-if clause.

it didn't help(

My project suffered the same issue. And the root cause turned out to be, unintended and careless reactivity: some object whose internal state doesn't need to be reactive was in the props. But Vue 3 nor eslint don't (and probably can't) gave me any warnings, so it was extremely hard to find. I ended up with several conditional break point in the core vue3 reactivity source files.

Maybe my case is similar to yours @nevermin9, so let me explain in more detail. The type error occurs because this.chart is undefined in watch handler of options property of Chart. Where is this.chart defined? In the mounted() hook. Now before mount, vue sets up render, and in the course, my computed property was run because it was set to options property. NORMALLY, then the mounted() hook would have run. BUT while my computed property was run, some state of some object is changed which triggered recomputation AND the watch handler. BEFORE mounted(). And BOOM.

The problem was fixed by moving the culprit out of props into provide.