From dc51eb979e5db6fb57a07d52efa36ef03e83dfcf Mon Sep 17 00:00:00 2001 From: Martin Schuhfuss Date: Thu, 30 May 2024 13:05:45 -0700 Subject: [PATCH] fix(info-window): fix reappearing InfoWindows (#393) An InfoWindow that is attached to a Marker or AdvancedMarker as anchor would occasionally show up again after being unmounted. This can happen when the anchor is removed from the map before the InfoWindow is unmounted but gets re-added to the map at a later point. This is caused by intended behavior in the maps API, and the only workaround for this right now is to forcefully disconnect the InfoWindow from its anchor. See here for more details: https://issuetracker.google.com/issues/343750849 Co-Authored-By: Nick Schnelle nick.schnelle@bupa.com.au --- src/components/info-window.tsx | 25 +++++++++++++++++-------- src/hooks/use-maps-event-listener.ts | 2 +- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/components/info-window.tsx b/src/components/info-window.tsx index b5f25f69..c1306bf6 100644 --- a/src/components/info-window.tsx +++ b/src/components/info-window.tsx @@ -69,23 +69,21 @@ export const InfoWindow = (props: PropsWithChildren) => { // intentionally shadowing the state variables here const infoWindow = new google.maps.InfoWindow(infoWindowOptions); const contentContainer = document.createElement('div'); - infoWindow.setContent(contentContainer); setInfoWindow(infoWindow); setContentContainer(contentContainer); - // cleanup: remove infoWindow, all event listeners and contentElement + // unmount: remove infoWindow and contentElement return () => { - google.maps.event.clearInstanceListeners(infoWindow); - - infoWindow.close(); + infoWindow.setContent(null); contentContainer.remove(); + setInfoWindow(null); setContentContainer(null); }; }, - // `infoWindowOptions` is missing from dependencies: + // `infoWindowOptions` and `pixelOffset` are missing from dependencies: // // We don't want to re-create the infowindow instance // when the options change. @@ -121,7 +119,8 @@ export const InfoWindow = (props: PropsWithChildren) => { infoWindow.setOptions(infoWindowOptions); }, - // dependency `infoWindow` isn't needed since options are passed to the constructor + // dependency `infoWindow` isn't needed since options are also passed + // to the constructor when a new infoWindow is created. // eslint-disable-next-line react-hooks/exhaustive-deps [infoWindowOptions] ); @@ -136,8 +135,8 @@ export const InfoWindow = (props: PropsWithChildren) => { // `anchor === null` means an anchor is defined but not ready yet. if (!contentContainer || !infoWindow || anchor === null) return; + const isOpenedWithAnchor = !!anchor; const openOptions: google.maps.InfoWindowOpenOptions = {map}; - if (anchor) { openOptions.anchor = anchor; } @@ -147,6 +146,16 @@ export const InfoWindow = (props: PropsWithChildren) => { } infoWindow.open(openOptions); + + return () => { + // Note: when the infowindow has an anchor, it will automatically show up again when the + // anchor was removed from the map before infoWindow.close() is called but the it gets + // added back to the map after that. + // More information here: https://issuetracker.google.com/issues/343750849 + if (isOpenedWithAnchor) infoWindow.set('anchor', null); + + infoWindow.close(); + }; }, [infoWindow, contentContainer, anchor, map, shouldFocus]); return ( diff --git a/src/hooks/use-maps-event-listener.ts b/src/hooks/use-maps-event-listener.ts index 0595a333..c105e0a8 100644 --- a/src/hooks/use-maps-event-listener.ts +++ b/src/hooks/use-maps-event-listener.ts @@ -15,6 +15,6 @@ export function useMapsEventListener void>( const listener = google.maps.event.addListener(target, name, callback); - return () => listener?.remove(); + return () => listener.remove(); }, [target, name, callback]); }