From cf15fe0d03529432e1ef1741036ab5b60af197d2 Mon Sep 17 00:00:00 2001
From: David Aaron Suddjian <1858430+suddjian@users.noreply.github.com>
Date: Mon, 7 Jun 2021 14:04:56 -0700
Subject: [PATCH] fix(dashboard): custom css should be removed on unmount
(#15025)
* fix(dashboard): custom css should be removed on unmount
* better comment
* remove unnecessary typecast
* move type to top level scope
---
.../dashboard/containers/DashboardPage.tsx | 40 ++++++++-----------
...{injectCustomCss.js => injectCustomCss.ts} | 36 ++++++++++++-----
2 files changed, 43 insertions(+), 33 deletions(-)
rename superset-frontend/src/dashboard/util/{injectCustomCss.js => injectCustomCss.ts} (66%)
diff --git a/superset-frontend/src/dashboard/containers/DashboardPage.tsx b/superset-frontend/src/dashboard/containers/DashboardPage.tsx
index 580df62b3..e5897d18c 100644
--- a/superset-frontend/src/dashboard/containers/DashboardPage.tsx
+++ b/superset-frontend/src/dashboard/containers/DashboardPage.tsx
@@ -26,7 +26,6 @@ import {
useDashboardDatasets,
} from 'src/common/hooks/apiResources';
import { ResourceStatus } from 'src/common/hooks/apiResources/apiResources';
-import { usePrevious } from 'src/common/hooks/usePrevious';
import { hydrateDashboard } from 'src/dashboard/actions/hydrate';
import injectCustomCss from 'src/dashboard/util/injectCustomCss';
@@ -42,14 +41,10 @@ const DashboardContainer = React.lazy(
const DashboardPage: FC = () => {
const dispatch = useDispatch();
const { idOrSlug } = useParams<{ idOrSlug: string }>();
- const [isLoaded, setLoaded] = useState(false);
+ const [isHydrated, setHydrated] = useState(false);
const dashboardResource = useDashboard(idOrSlug);
const chartsResource = useDashboardCharts(idOrSlug);
const datasetsResource = useDashboardDatasets(idOrSlug);
- const isLoading = [dashboardResource, chartsResource, datasetsResource].some(
- resource => resource.status === ResourceStatus.LOADING,
- );
- const wasLoading = usePrevious(isLoading);
const error = [dashboardResource, chartsResource, datasetsResource].find(
resource => resource.status === ResourceStatus.ERROR,
)?.error;
@@ -57,16 +52,22 @@ const DashboardPage: FC = () => {
useEffect(() => {
if (dashboardResource.result) {
document.title = dashboardResource.result.dashboard_title;
+ if (dashboardResource.result.css) {
+ // returning will clean up custom css
+ // when dashboard unmounts or changes
+ return injectCustomCss(dashboardResource.result.css);
+ }
}
+ return () => {};
}, [dashboardResource.result]);
+ const shouldBeHydrated =
+ dashboardResource.status === ResourceStatus.COMPLETE &&
+ chartsResource.status === ResourceStatus.COMPLETE &&
+ datasetsResource.status === ResourceStatus.COMPLETE;
+
useEffect(() => {
- if (
- wasLoading &&
- dashboardResource.status === ResourceStatus.COMPLETE &&
- chartsResource.status === ResourceStatus.COMPLETE &&
- datasetsResource.status === ResourceStatus.COMPLETE
- ) {
+ if (shouldBeHydrated) {
dispatch(
hydrateDashboard(
dashboardResource.result,
@@ -74,20 +75,13 @@ const DashboardPage: FC = () => {
datasetsResource.result,
),
);
- injectCustomCss(dashboardResource.result.css);
- setLoaded(true);
+ setHydrated(true);
}
- }, [
- dispatch,
- wasLoading,
- dashboardResource,
- chartsResource,
- datasetsResource,
- ]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [shouldBeHydrated]);
if (error) throw error; // caught in error boundary
-
- if (!isLoaded) return ;
+ if (!isHydrated) return ;
return ;
};
diff --git a/superset-frontend/src/dashboard/util/injectCustomCss.js b/superset-frontend/src/dashboard/util/injectCustomCss.ts
similarity index 66%
rename from superset-frontend/src/dashboard/util/injectCustomCss.js
rename to superset-frontend/src/dashboard/util/injectCustomCss.ts
index 4f6238aad..b7db2b03d 100644
--- a/superset-frontend/src/dashboard/util/injectCustomCss.js
+++ b/superset-frontend/src/dashboard/util/injectCustomCss.ts
@@ -16,19 +16,31 @@
* specific language governing permissions and limitations
* under the License.
*/
-export default function injectCustomCss(css) {
+
+function createStyleElement(className: string) {
+ const style = document.createElement('style');
+ style.className = className;
+ style.type = 'text/css';
+ return style;
+}
+
+// The original, non-typescript code referenced `style.styleSheet`.
+// I can't find what sort of element would have a styleSheet property,
+// so have created this type to satisfy TS without changing behavior.
+type MysteryStyleElement = {
+ styleSheet: {
+ cssText: string;
+ };
+};
+
+export default function injectCustomCss(css: string) {
const className = 'CssEditor-css';
const head = document.head || document.getElementsByTagName('head')[0];
- let style = document.querySelector(`.${className}`);
+ const style: HTMLStyleElement =
+ document.querySelector(`.${className}`) || createStyleElement(className);
- if (!style) {
- style = document.createElement('style');
- style.className = className;
- style.type = 'text/css';
- }
-
- if (style.styleSheet) {
- style.styleSheet.cssText = css;
+ if ('styleSheet' in style) {
+ (style as MysteryStyleElement).styleSheet.cssText = css;
} else {
style.innerHTML = css;
}
@@ -45,4 +57,8 @@ export default function injectCustomCss(css) {
*/
head.appendChild(style);
+
+ return function removeCustomCSS() {
+ style.remove();
+ };
}