You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ms...@apache.org on 2026/05/04 16:41:16 UTC

(superset) 01/01: fix(embedded): prevent duplicate React root on rehandshake

This is an automated email from the ASF dual-hosted git repository.

msyavuz pushed a commit to branch react18-embedded-root
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 88bedebd0971f8739f667e8856d919d7a7b901be
Author: Mehmet Salih Yavuz <sa...@proton.me>
AuthorDate: Mon May 4 19:40:57 2026 +0300

    fix(embedded): prevent duplicate React root on rehandshake
    
    The `started` flag and the inline `createRoot(appMountPoint).render(...)`
    call both lived inside the 'message' event handler, so each port-transfer
    event reset `started` to false and could invoke `createRoot` a second time
    on the same DOM node — which React 18 warns about and where behavior is
    undefined.
    
    Hoist `started` and a lazily-created `root` to module scope so that
    re-handshakes reuse the existing root, and unmount it in the existing
    `beforeunload` handler.
---
 superset-frontend/src/embedded/index.tsx | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/superset-frontend/src/embedded/index.tsx b/superset-frontend/src/embedded/index.tsx
index 4d72d5815b4..4ea1162aa24 100644
--- a/superset-frontend/src/embedded/index.tsx
+++ b/superset-frontend/src/embedded/index.tsx
@@ -19,7 +19,7 @@
 import 'src/public-path';
 
 import { lazy, Suspense } from 'react';
-import { createRoot } from 'react-dom/client';
+import { createRoot, type Root } from 'react-dom/client';
 import { BrowserRouter as Router, Route } from 'react-router-dom';
 import { Global } from '@emotion/react';
 import { t } from '@apache-superset/core/translation';
@@ -150,6 +150,8 @@ if (!window.parent || window.parent === window) {
 // }
 
 let displayedUnauthorizedToast = false;
+let root: Root | null = null;
+let started = false;
 
 /**
  * If there is a problem with the guest token, we will start getting
@@ -189,7 +191,10 @@ function start() {
         type: USER_LOADED,
         user: result,
       });
-      createRoot(appMountPoint).render(<EmbeddedApp />);
+      if (!root) {
+        root = createRoot(appMountPoint);
+      }
+      root.render(<EmbeddedApp />);
     },
     err => {
       // something is most likely wrong with the guest token
@@ -243,8 +248,6 @@ window.addEventListener('message', function embeddedPageInitializer(event) {
       debug: debugMode,
     });
 
-    let started = false;
-
     Switchboard.defineMethod(
       'guestToken',
       ({ guestToken }: { guestToken: string }) => {
@@ -322,7 +325,7 @@ window.addEventListener('message', function embeddedPageInitializer(event) {
   }
 });
 
-// Clean up theme controller on page unload
+// Clean up theme controller and unmount React root on page unload
 window.addEventListener('beforeunload', () => {
   try {
     const controller = getThemeController();
@@ -333,6 +336,10 @@ window.addEventListener('beforeunload', () => {
   } catch (error) {
     logging.warn('Failed to destroy theme controller:', error);
   }
+  if (root) {
+    root.unmount();
+    root = null;
+  }
 });
 
 log('embed page is ready to receive messages');