From ea6ed17da56d7f5412cb42ed55dbdc552dbd8bad Mon Sep 17 00:00:00 2001 From: mikeyfarina Date: Thu, 10 Sep 2026 18:41:49 -0400 Subject: [PATCH] Fix reuseMaps infinite loop when reusing into the same container --- .../main/src/mapbox-legacy/mapbox/mapbox.ts | 18 ++++++++++-------- modules/react-mapbox/src/mapbox/mapbox.ts | 18 ++++++++++-------- .../react-maplibre/src/maplibre/maplibre.ts | 18 ++++++++++-------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/modules/main/src/mapbox-legacy/mapbox/mapbox.ts b/modules/main/src/mapbox-legacy/mapbox/mapbox.ts index 5e3f295dd..b8fea5b2c 100644 --- a/modules/main/src/mapbox-legacy/mapbox/mapbox.ts +++ b/modules/main/src/mapbox-legacy/mapbox/mapbox.ts @@ -244,9 +244,16 @@ export default class Mapbox { // intoto the new container from the props. // Step 1: reparenting child nodes from old container to new container const oldContainer = map.getContainer(); - container.className = oldContainer.className; - while (oldContainer.childNodes.length > 0) { - container.appendChild(oldContainer.childNodes[0]); + // The old and new containers are the same node when effects re-run without + // the DOM being recreated (React StrictMode replay, Activity/bfcache + // restore) - moving childNodes[0] to the end of the same node would then + // loop forever, and the children are already in place. + if (oldContainer !== container) { + container.className = oldContainer.className; + oldContainer.querySelector('[mapboxgl-children]')?.remove(); + while (oldContainer.childNodes.length > 0) { + container.appendChild(oldContainer.childNodes[0]); + } } // Step 2: replace the internal container with new container from the react component // @ts-ignore @@ -365,11 +372,6 @@ export default class Mapbox { /* eslint-enable complexity,max-statements */ recycle() { - // Clean up unnecessary elements before storing for reuse. - const container = this.map.getContainer(); - const children = container.querySelector('[mapboxgl-children]'); - children?.remove(); - Mapbox.savedMaps.push(this); } diff --git a/modules/react-mapbox/src/mapbox/mapbox.ts b/modules/react-mapbox/src/mapbox/mapbox.ts index bb40fd7c3..b13ee82cb 100644 --- a/modules/react-mapbox/src/mapbox/mapbox.ts +++ b/modules/react-mapbox/src/mapbox/mapbox.ts @@ -251,9 +251,16 @@ export default class Mapbox { // intoto the new container from the props. // Step 1: reparenting child nodes from old container to new container const oldContainer = map.getContainer(); - container.className = oldContainer.className; - while (oldContainer.childNodes.length > 0) { - container.appendChild(oldContainer.childNodes[0]); + // The old and new containers are the same node when effects re-run without + // the DOM being recreated (React StrictMode replay, Activity/bfcache + // restore) - moving childNodes[0] to the end of the same node would then + // loop forever, and the children are already in place. + if (oldContainer !== container) { + container.className = oldContainer.className; + oldContainer.querySelector('[mapboxgl-children]')?.remove(); + while (oldContainer.childNodes.length > 0) { + container.appendChild(oldContainer.childNodes[0]); + } } // Step 2: replace the internal container with new container from the react component // @ts-ignore @@ -382,11 +389,6 @@ export default class Mapbox { /* eslint-enable complexity,max-statements */ recycle() { - // Clean up unnecessary elements before storing for reuse. - const container = this.map.getContainer(); - const children = container.querySelector('[mapboxgl-children]'); - children?.remove(); - Mapbox.savedMaps.push(this); } diff --git a/modules/react-maplibre/src/maplibre/maplibre.ts b/modules/react-maplibre/src/maplibre/maplibre.ts index cd3e35205..99983b68e 100644 --- a/modules/react-maplibre/src/maplibre/maplibre.ts +++ b/modules/react-maplibre/src/maplibre/maplibre.ts @@ -236,9 +236,16 @@ export default class Maplibre { // intoto the new container from the props. // Step 1: reparenting child nodes from old container to new container const oldContainer = map.getContainer(); - container.className = oldContainer.className; - while (oldContainer.childNodes.length > 0) { - container.appendChild(oldContainer.childNodes[0]); + // The old and new containers are the same node when effects re-run without + // the DOM being recreated (React StrictMode replay, Activity/bfcache + // restore) - moving childNodes[0] to the end of the same node would then + // loop forever, and the children are already in place. + if (oldContainer !== container) { + container.className = oldContainer.className; + oldContainer.querySelector('[mapboxgl-children]')?.remove(); + while (oldContainer.childNodes.length > 0) { + container.appendChild(oldContainer.childNodes[0]); + } } // Step 2: replace the internal container with new container from the react component // @ts-ignore @@ -361,11 +368,6 @@ export default class Maplibre { /* eslint-enable complexity,max-statements */ recycle() { - // Clean up unnecessary elements before storing for reuse. - const container = this.map.getContainer(); - const children = container.querySelector('[mapboxgl-children]'); - children?.remove(); - Maplibre.savedMaps.push(this); }