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:15 UTC

(superset) branch react18-embedded-root created (now 88bedebd097)

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

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


      at 88bedebd097 fix(embedded): prevent duplicate React root on rehandshake

This branch includes the following new commits:

     new 88bedebd097 fix(embedded): prevent duplicate React root on rehandshake

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



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

Posted by ms...@apache.org.
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');