From cc5d3b27156255155d64feab51117f9b05020939 Mon Sep 17 00:00:00 2001 From: yharby Date: Wed, 3 Dec 2025 16:46:43 +0200 Subject: [PATCH 1/3] fix(jupyter): call handleResize on mount for correct initial viewport sizing The Kepler.gl map doesn't fill the viewport on initial render because the useEffect in app.js adds a resize listener but never calls handleResize() on mount. This causes the component to use default dimensions (800x400) until a resize event occurs. Changes: - app.js: Call handleResize() on initial mount in useEffect - root.js: Upgrade to React 18 createRoot API (replaces deprecated ReactDOM.render) Fixes #3252 --- .../kepler.gl-jupyter/js/lib/keplergl/components/app.js | 2 ++ .../kepler.gl-jupyter/js/lib/keplergl/components/root.js | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) 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..a257138e3b 100644 --- a/bindings/kepler.gl-jupyter/js/lib/keplergl/components/app.js +++ b/bindings/kepler.gl-jupyter/js/lib/keplergl/components/app.js @@ -71,6 +71,8 @@ function App() { 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..3a3ac8f613 100644 --- a/bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js +++ b/bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js @@ -2,7 +2,7 @@ // Copyright contributors to the kepler.gl project import React from 'react'; -import ReactDOM from 'react-dom'; +import {createRoot} from 'react-dom/client'; import {Provider} from 'react-redux'; import App from './app'; @@ -13,7 +13,9 @@ function renderRoot({id, store, ele}) { ); - ReactDOM.render(, ele); + // Use React 18 createRoot API + const root = createRoot(ele); + root.render(); } export default renderRoot; From e6d70d0f3aeed08a7394201b2336b1ae445b62ea Mon Sep 17 00:00:00 2001 From: yharby Date: Wed, 3 Dec 2025 17:32:56 +0200 Subject: [PATCH 2/3] fix(jupyter): migrate to React 18 createRoot API and fix data loading - Update root.js to use React 18 createRoot API instead of deprecated ReactDOM.render - Add DataLoader component to handle data loading after Provider mount - Move data loading from main.js into React component lifecycle - This ensures Redux Provider subscriptions are active before dispatching actions - Fixes data/config not loading in HTML export mode --- .../js/lib/keplergl/components/root.js | 36 +++++++++++++++---- .../kepler.gl-jupyter/js/lib/keplergl/main.js | 9 ++--- 2 files changed, 31 insertions(+), 14 deletions(-) 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 3a3ac8f613..975c6478e0 100644 --- a/bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js +++ b/bindings/kepler.gl-jupyter/js/lib/keplergl/components/root.js @@ -1,21 +1,43 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import React from 'react'; +import React, {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 = () => ( +// 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 + const root = createRoot(ele); + root.render( + ); - - // Use React 18 createRoot API - const root = createRoot(ele); - root.render(); } 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); From 58242cc9812e9c97069ebfe27fd8dc8a2ab7b5c2 Mon Sep 17 00:00:00 2001 From: yharby Date: Wed, 3 Dec 2025 22:45:50 +0200 Subject: [PATCH 3/3] fix(jupyter): simplify resize logic and add WeakMap for root instances - Simplify handleResize to directly set dimensions without stale state comparison - Add WeakMap to store and reuse createRoot instances, preventing memory leaks - Keep minimal change approach for viewport sizing fix --- .../js/lib/keplergl/components/app.js | 13 +++++-------- .../js/lib/keplergl/components/root.js | 14 +++++++++++--- 2 files changed, 16 insertions(+), 11 deletions(-) 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 a257138e3b..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,15 +58,12 @@ 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); 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 975c6478e0..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,13 +1,16 @@ // SPDX-License-Identifier: MIT // Copyright contributors to the kepler.gl project -import React, {useEffect, useRef} from 'react'; +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'; +// 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); @@ -30,8 +33,13 @@ function DataLoader({store, onRenderComplete}) { } function renderRoot({id, store, ele, onRenderComplete}) { - // Use React 18 createRoot API - const root = createRoot(ele); + // 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(