Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/components/__tests__/map.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<GoogleMap mapId={'broken-cache'} reuseMaps center={center} zoom={12} />,
{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(
<GoogleMap
mapId={'broken-cache'}
reuseMaps
center={center}
zoom={12}
/>,
{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', () => {
Expand Down
21 changes: 18 additions & 3 deletions src/components/map/use-map-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down