diff --git a/src/components/__tests__/map.test.tsx b/src/components/__tests__/map.test.tsx
index 305be0b0..b621f7ed 100644
--- a/src/components/__tests__/map.test.tsx
+++ b/src/components/__tests__/map.test.tsx
@@ -209,6 +209,42 @@ describe('map instance caching', () => {
"map isn't recreated when unmounting and remounting with regular changed options"
);
test.todo('removed options are handled correctly');
+
+ test("doesn't crash when remounting with a broken cached map instance", async () => {
+ // simulates the case where the initial map-creation failed (e.g. the Maps
+ // JavaScript API didn't load correctly), leaving a cached map whose
+ // getDiv() doesn't return a usable DOM node. Remounting must not throw.
+ const center = {lat: 53.55, lng: 10.05};
+
+ const {unmount} = render(
+ ,
+ {wrapper}
+ );
+ await waitFor(() => expect(screen.getByTestId('map')).toBeInTheDocument());
+
+ // make the cached instance return a non-Node from getDiv()
+ const cachedMap = mockInstances.get(google.maps.Map).at(-1)!;
+ jest.mocked(cachedMap.getDiv).mockReturnValue(undefined as never);
+
+ unmount();
+ createMapSpy.mockReset();
+
+ expect(() =>
+ render(
+ ,
+ {wrapper}
+ )
+ ).not.toThrow();
+
+ await waitFor(() => expect(screen.getByTestId('map')).toBeInTheDocument());
+ // a fresh map instance should have been created instead of reusing the broken one
+ expect(createMapSpy).toHaveBeenCalled();
+ });
});
describe('camera configuration', () => {
diff --git a/src/components/map/use-map-instance.ts b/src/components/map/use-map-instance.ts
index b2b21e4c..1e221ee0 100644
--- a/src/components/map/use-map-instance.ts
+++ b/src/components/map/use-map-instance.ts
@@ -138,9 +138,21 @@ export function useMapInstance(
let mapDiv: HTMLElement;
let map: google.maps.Map;
- if (reuseMaps && CachedMapStack.has(cacheKey)) {
- map = CachedMapStack.pop(cacheKey) as google.maps.Map;
- mapDiv = map.getDiv();
+ // a cached map can end up in a broken state (e.g. when the initial
+ // map-creation failed because the Maps JavaScript API didn't load
+ // correctly). In that case `getDiv()` doesn't return a usable DOM node,
+ // so we have to discard the cached instance instead of trying to reuse it.
+ const cachedMap =
+ reuseMaps && CachedMapStack.has(cacheKey)
+ ? (CachedMapStack.pop(cacheKey) as google.maps.Map)
+ : null;
+ const cachedMapDiv = cachedMap?.getDiv();
+ const reusedMap =
+ cachedMap && cachedMapDiv instanceof Node ? cachedMap : null;
+
+ if (reusedMap) {
+ map = reusedMap;
+ mapDiv = cachedMapDiv as HTMLElement;
container.appendChild(mapDiv);
map.setOptions(mapOptions);
@@ -151,6 +163,9 @@ export function useMapInstance(
// the map.
setTimeout(() => map.moveCamera({}), 0);
} else {
+ // discard a broken cached instance so it doesn't get pushed back
+ if (cachedMap) google.maps.event.clearInstanceListeners(cachedMap);
+
mapDiv = document.createElement('div');
mapDiv.style.height = '100%';
container.appendChild(mapDiv);