diff --git a/bindings/kepler.gl-jupyter/js/lib/keplergl/components/app.js b/bindings/kepler.gl-jupyter/js/lib/keplergl/components/app.js
index 281abe195e..9e1f3811a5 100644
--- a/bindings/kepler.gl-jupyter/js/lib/keplergl/components/app.js
+++ b/bindings/kepler.gl-jupyter/js/lib/keplergl/components/app.js
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project
-import React, {useEffect, useState, useRef} from 'react';
+import {useEffect, useState, useRef} from 'react';
import styled from 'styled-components';
const ReactHelmet = require('react-helmet');
const Helmet = ReactHelmet ? ReactHelmet.Helmet : null;
@@ -58,19 +58,18 @@ function App() {
const width = rootElm.current.offsetWidth;
const height = rootElm.current.offsetHeight;
- const dimensionToSet = {
- ...(width && width !== windowDimension.width ? {width} : {}),
- ...(height && height !== windowDimension.height ? {height} : {})
- };
-
- setDimension(dimensionToSet);
+ if (width && height) {
+ setDimension({width, height});
+ }
};
- // in Jupyter Lab, parent component has transition when window resize.
+ // in Jupyter Lab, parent component has transition when window resize.
// need to delay call to get the final parent width,
const resizeDelay = () => window.setTimeout(handleResize, 500);
useEffect(() => {
+ // Call handleResize on mount to set initial dimensions
+ handleResize();
window.addEventListener('resize', resizeDelay);
return () => window.removeEventListener('resize', resizeDelay);
}, []);
diff --git a/bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js b/bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js
index 03c6f96c44..0046ceabc6 100644
--- a/bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js
+++ b/bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js
@@ -1,19 +1,51 @@
// SPDX-License-Identifier: MIT
// Copyright contributors to the kepler.gl project
-import React from 'react';
-import ReactDOM from 'react-dom';
+import {useEffect, useRef} from 'react';
+import {createRoot} from 'react-dom/client';
import {Provider} from 'react-redux';
import App from './app';
+import Window from 'global/window';
+import {addDataConfigToKeplerGl} from '../kepler.gl';
-function renderRoot({id, store, ele}) {
- const Root = () => (
+// Store root instances to avoid creating multiple roots for the same element
+const rootInstances = new WeakMap();
+
+// Separate component to handle data loading after mount
+function DataLoader({store, onRenderComplete}) {
+ const hasLoadedData = useRef(false);
+
+ useEffect(() => {
+ // This runs AFTER component tree is mounted and Provider is fully subscribed
+ // Load data from Window.__keplerglDataConfig (HTML export mode)
+ if (!hasLoadedData.current && Window.__keplerglDataConfig) {
+ hasLoadedData.current = true;
+ const {data, config, options} = Window.__keplerglDataConfig;
+ addDataConfigToKeplerGl({data, config, options, store});
+ }
+ // Signal render complete for callback-based loading (Jupyter widget mode)
+ if (onRenderComplete) {
+ onRenderComplete();
+ }
+ }, [store, onRenderComplete]);
+
+ return null;
+}
+
+function renderRoot({id, store, ele, onRenderComplete}) {
+ // Use React 18 createRoot API - reuse existing root if available
+ let root = rootInstances.get(ele);
+ if (!root) {
+ root = createRoot(ele);
+ rootInstances.set(ele, root);
+ }
+
+ root.render(
+
);
-
- ReactDOM.render(, ele);
}
export default renderRoot;
diff --git a/bindings/kepler.gl-jupyter/js/lib/keplergl/main.js b/bindings/kepler.gl-jupyter/js/lib/keplergl/main.js
index 48abea3076..c3daf294ec 100644
--- a/bindings/kepler.gl-jupyter/js/lib/keplergl/main.js
+++ b/bindings/kepler.gl-jupyter/js/lib/keplergl/main.js
@@ -5,8 +5,6 @@
import createAppStore from './store';
import renderRoot from './components/root';
import document from 'global/document';
-import Window from 'global/window';
-import {addDataConfigToKeplerGl} from './kepler.gl';
const map = (function initKeplerGl() {
const id = 'keplergl-0';
@@ -18,6 +16,8 @@ const map = (function initKeplerGl() {
return {
render: () => {
+ // Data loading is now handled inside renderRoot via useEffect
+ // This ensures Redux Provider subscriptions are active before dispatching
renderRoot({id, store, ele: divElmt});
},
store
@@ -25,8 +25,3 @@ const map = (function initKeplerGl() {
})();
map.render();
-
-(function loadDataConfig(keplerGlMap) {
- const {data, config, options} = Window.__keplerglDataConfig || {};
- addDataConfigToKeplerGl({data, config, options, store: keplerGlMap.store});
-})(map);