Browse Source

Merge pull request #15113 from grafana/fix/explore-set-state-on-unmounted

Fix setState on unmounted component in Loki and Prometheus QueryField
Torkel Ödegaard 7 years ago
parent
commit
0216f087b7

+ 22 - 0
public/app/core/utils/CancelablePromise.ts

@@ -0,0 +1,22 @@
+// https://github.com/facebook/react/issues/5465
+
+export interface CancelablePromise<T> {
+  promise: Promise<T>;
+  cancel: () => void;
+}
+
+export const makePromiseCancelable = <T>(promise: Promise<T>): CancelablePromise<T> => {
+  let hasCanceled_ = false;
+
+  const wrappedPromise = new Promise<T>((resolve, reject) => {
+    promise.then(val => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)));
+    promise.catch(error => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error)));
+  });
+
+  return {
+    promise: wrappedPromise,
+    cancel() {
+      hasCanceled_ = true;
+    },
+  };
+};

+ 17 - 3
public/app/plugins/datasource/loki/components/LokiQueryField.tsx

@@ -16,6 +16,7 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
 // Types
 import { LokiQuery } from '../types';
 import { TypeaheadOutput } from 'app/types/explore';
+import { makePromiseCancelable, CancelablePromise } from 'app/core/utils/CancelablePromise';
 
 const PRISM_SYNTAX = 'promql';
 
@@ -85,6 +86,7 @@ class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, LokiQueryF
   languageProvider: any;
   modifiedSearch: string;
   modifiedQuery: string;
+  languageProviderInitializationPromise: CancelablePromise<any>;
 
   constructor(props: LokiQueryFieldProps, context) {
     super(props, context);
@@ -112,12 +114,24 @@ class LokiQueryField extends React.PureComponent<LokiQueryFieldProps, LokiQueryF
 
   componentDidMount() {
     if (this.languageProvider) {
-      this.languageProvider
-        .start()
+      this.languageProviderInitializationPromise = makePromiseCancelable(this.languageProvider.start());
+
+      this.languageProviderInitializationPromise.promise
         .then(remaining => {
           remaining.map(task => task.then(this.onUpdateLanguage).catch(() => {}));
         })
-        .then(() => this.onUpdateLanguage());
+        .then(() => this.onUpdateLanguage())
+        .catch(({ isCanceled }) => {
+          if (isCanceled) {
+            console.warn('LokiQueryField has unmounted, language provider intialization was canceled');
+          }
+        });
+    }
+  }
+
+  componentWillUnmount() {
+    if (this.languageProviderInitializationPromise) {
+      this.languageProviderInitializationPromise.cancel();
     }
   }
 

+ 16 - 3
public/app/plugins/datasource/prometheus/components/PromQueryField.tsx

@@ -12,6 +12,7 @@ import BracesPlugin from 'app/features/explore/slate-plugins/braces';
 import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
 import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
 import { PromQuery } from '../types';
+import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
 
 const HISTOGRAM_GROUP = '__histograms__';
 const METRIC_MARK = 'metric';
@@ -104,6 +105,7 @@ interface PromQueryFieldState {
 class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryFieldState> {
   plugins: any[];
   languageProvider: any;
+  languageProviderInitializationPromise: CancelablePromise<any>;
 
   constructor(props: PromQueryFieldProps, context) {
     super(props, context);
@@ -129,12 +131,23 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
 
   componentDidMount() {
     if (this.languageProvider) {
-      this.languageProvider
-        .start()
+      this.languageProviderInitializationPromise = makePromiseCancelable(this.languageProvider.start());
+      this.languageProviderInitializationPromise.promise
         .then(remaining => {
           remaining.map(task => task.then(this.onUpdateLanguage).catch(() => {}));
         })
-        .then(() => this.onUpdateLanguage());
+        .then(() => this.onUpdateLanguage())
+        .catch(({ isCanceled }) => {
+          if (isCanceled) {
+            console.warn('PromQueryField has unmounted, language provider intialization was canceled');
+          }
+        });
+    }
+  }
+
+  componentWillUnmount() {
+    if (this.languageProviderInitializationPromise) {
+      this.languageProviderInitializationPromise.cancel();
     }
   }