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

ephraimanierobi pushed a commit to branch v3-1-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-1-test by this push:
     new f31715ce56c Fix react apps plugins router (#61206)
f31715ce56c is described below

commit f31715ce56cc72cc5d97513e943367a5fb86db47
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Thu Jan 29 14:30:11 2026 +0100

    Fix react apps plugins router (#61206)
---
 .../src/airflow/ui/src/pages/ExternalView.tsx      |  6 ++++--
 .../src/airflow/ui/src/pages/ReactPlugin.tsx       | 24 ++++++++++++++--------
 2 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx 
b/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
index a1c80cdf2f3..c74d9b25a4b 100644
--- a/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
@@ -18,7 +18,7 @@
  */
 import { Box } from "@chakra-ui/react";
 import { useTranslation } from "react-i18next";
-import { useParams } from "react-router-dom";
+import { useLocation, useParams } from "react-router-dom";
 
 import { usePluginServiceGetPlugins } from "openapi/queries";
 import { ProgressBar } from "src/components/ui";
@@ -32,6 +32,8 @@ export const ExternalView = () => {
   const { page } = useParams();
   const { data: pluginData, isLoading } = usePluginServiceGetPlugins();
 
+  const { pathname } = useLocation();
+
   const externalView =
     page === "legacy-fab-views"
       ? {
@@ -82,7 +84,7 @@ export const ExternalView = () => {
         m={-2} // Compensate for parent padding
         minHeight={0}
       >
-        <ReactPlugin reactApp={reactApp} />
+        <ReactPlugin key={pathname} reactApp={reactApp} />
       </Box>
     );
   }
diff --git a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx 
b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
index 1c1800044f7..12144d17869 100644
--- a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
@@ -34,19 +34,27 @@ type PluginComponentType = FC<{
 export const ReactPlugin = ({ reactApp }: { readonly reactApp: 
ReactAppResponse }) => {
   const { dagId, mapIndex, runId, taskId } = useParams();
 
-  const Plugin = lazy(() =>
-    // We are assuming the plugin manager is trusted and the bundle_url is safe
+  // If the plugin component was already registered on the global object by a 
previous load,
+  // render it directly without going through Suspense/lazy (avoids flashing 
the spinner).
+  const existing = (globalThis as Record<string, unknown>)[reactApp.name];
+
+  if (typeof existing === "function") {
+    const Plugin = existing as PluginComponentType;
+
+    return <Plugin dagId={dagId} mapIndex={mapIndex} runId={runId} 
taskId={taskId} />;
+  }
+
+  // Otherwise, lazy-load the bundle once. When it resolves, it must set a 
function component
+  // under globalThis[reactApp.name], which we then use as the default export.
+  const LazyPlugin = lazy(() =>
     import(/* @vite-ignore */ reactApp.bundle_url)
       .then(() => {
-        // Store components in globalThis[reactApp.name] to avoid conflicts 
with the shared globalThis.AirflowPlugin
-        // global variable.
         let pluginComponent = (globalThis as Record<string, 
unknown>)[reactApp.name] as
           | PluginComponentType
           | undefined;
 
         if (pluginComponent === undefined) {
           pluginComponent = (globalThis as Record<string, 
unknown>).AirflowPlugin as PluginComponentType;
-
           (globalThis as Record<string, unknown>)[reactApp.name] = 
pluginComponent;
         }
 
@@ -59,15 +67,13 @@ export const ReactPlugin = ({ reactApp }: { readonly 
reactApp: ReactAppResponse
       .catch((error: unknown) => {
         console.error("Component Failed Loading:", error);
 
-        return {
-          default: ErrorPage,
-        };
+        return { default: ErrorPage };
       }),
   );
 
   return (
     <Suspense fallback={<Spinner />}>
-      <Plugin dagId={dagId} mapIndex={mapIndex} runId={runId} taskId={taskId} 
/>
+      <LazyPlugin dagId={dagId} mapIndex={mapIndex} runId={runId} 
taskId={taskId} />
     </Suspense>
   );
 };

Reply via email to