/* eslint-disable complexity */ import React, {PropsWithChildren, useEffect, useRef, useState} from 'react'; import {createPortal} from 'react-dom'; import {useMap} from '../hooks/use-map'; /** * Props for the Info Window Component */ export type InfoWindowProps = google.maps.InfoWindowOptions & { onCloseClick?: () => void; anchor?: google.maps.Marker | google.maps.marker.AdvancedMarkerElement | null; shouldFocus?: boolean; }; /** * Component to render a Google Maps Info Window */ export const InfoWindow = (props: PropsWithChildren) => { const {children, anchor, shouldFocus, onCloseClick, ...infoWindowOptions} = props; const map = useMap(); const infoWindowRef = useRef(null); const [contentContainer, setContentContainer] = useState(null); // create infowindow once map is available useEffect(() => { if (!map) return; const newInfowindow = new google.maps.InfoWindow(infoWindowOptions); // Add content to info window const el = document.createElement('div'); newInfowindow.setContent(el); infoWindowRef.current = newInfowindow; setContentContainer(el); // Cleanup info window and event listeners on unmount return () => { google.maps.event.clearInstanceListeners(newInfowindow); newInfowindow.close(); el.remove(); setContentContainer(null); }; // `infoWindowOptions` is missing from dependencies: // // we don't want to re-render a whole new infowindow // when the options change to prevent flickering. // Update of infoWindow options is handled in the useEffect below. // // eslint-disable-next-line react-hooks/exhaustive-deps }, [map, children]); // Update infoWindowOptions useEffect(() => { infoWindowRef.current?.setOptions(infoWindowOptions); }, [infoWindowOptions]); // Handle the close click callback useEffect(() => { if (!infoWindowRef.current) return; let listener: google.maps.MapsEventListener | null = null; if (onCloseClick) { listener = google.maps.event.addListener( infoWindowRef.current, 'closeclick', onCloseClick ); } return () => { if (listener) listener.remove(); }; }, [onCloseClick]); // Open info window after content container is set useEffect(() => { // anchor === null means an anchor is defined but not ready yet. if (!contentContainer || !infoWindowRef.current || anchor === null) return; const openOptions: google.maps.InfoWindowOpenOptions = {map}; if (anchor) { openOptions.anchor = anchor; } if (shouldFocus !== undefined) { openOptions.shouldFocus = shouldFocus; } infoWindowRef.current.open(openOptions); }, [contentContainer, infoWindowRef, anchor, map, shouldFocus]); return ( <>{contentContainer !== null && createPortal(children, contentContainer)} ); };