From 37407afb84e8b42a77ccd1f66359146552b68e72 Mon Sep 17 00:00:00 2001 From: Kresna Date: Fri, 7 Aug 2026 15:51:16 +0700 Subject: [PATCH] fix(maps): stack overflow in map.resize() on moveend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix called map.resize() synchronously inside the moveend handler. MapLibre internally calls constrainInternal during resize, which fires another moveend within the same call stack → infinite recursion → RangeError: Maximum call stack size exceeded. Fix: guard flag + requestAnimationFrame so resize() runs after the current call stack clears. The flag ensures at most one pending resize at a time, and rAF breaks the synchronous recursion path entirely. Co-Authored-By: Claude Sonnet 4.6 --- src/islands/maps/MapExplorer.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/islands/maps/MapExplorer.tsx b/src/islands/maps/MapExplorer.tsx index 3a79fac..c6ce208 100644 --- a/src/islands/maps/MapExplorer.tsx +++ b/src/islands/maps/MapExplorer.tsx @@ -128,7 +128,15 @@ export default function MapExplorer({ lang = 'en' }: { lang?: Lang }) { // After any pan/zoom animation (flyTo, GeolocateControl, etc.) revalidate the // canvas size — without this, MapLibre reports stale dimensions and tiles don't // fill the viewport, leaving the map blank. - map.on('moveend', () => map.resize()); + // Guard + rAF: calling map.resize() synchronously inside moveend causes MapLibre + // to fire moveend again from within constrainInternal → stack overflow. Deferring + // to the next animation frame breaks the synchronous recursion. + let resizePending = false; + map.on('moveend', () => { + if (resizePending) return; + resizePending = true; + requestAnimationFrame(() => { resizePending = false; map.resize(); }); + }); // The container is mounted via a dynamically-imported island, so it can be // laid out after the map is created — resize once it (or its size) settles, // otherwise the map renders blank at 0×0.