Michael McGreal

Michael McGreal

Codesandbox Demo

Expand for work in progress Codesandbox Demo
// Type anything here! -> 
// note: this editor panel is resizable. Drag the border to expand
import { Wrapper } from '@googlemaps/react-wrapper'
import { useEffect, useRef, useState } from 'react'
import { locations } from './locations'
export default function MapApp() {
  const mapRef = useRef<HTMLDivElement>(null)
  const [map, setMap] = useState<google.maps.Map>()
  const [markers, setMarkers] = useState<google.maps.Marker[]>()
  const [showText, setShowText] = useState(true)
  const skip = useRef(false)
  useEffect(() => {
    if (mapRef.current && !map) {
      setMap(
        new google.maps.Map(mapRef.current, {
          center: { lat: 41.85, lng: -87.65 },
          zoom: 12,
          controls: false
        })
      )
    }
    if (map && !markers && !skip.current) {
      setMarkers(
        locations.map(
          (location) =>
            new google.maps.Marker({
              position: {
                lat: location.latitude,
                lng: location.longitude,
              },
              map: map,
            })
        )
      )
    }
  }, [mapRef, map, markers])
  return (
    <Wrapper 
    apiKey='AIzaSyBJAi9Zz1b8CcaYdUTvK4meFZtK5WOcPMA'
    >
    {!mapRef.current && (
        <p id="bugAlert-unrelatedToTutorial-deleteMe!" style={{ width: '100vw', height: '100vh', display: 'grid', placeContent: 'center', padding: 8  }}>Apologies, there is a bug in this codesanbox. <br/>To see the map, type anything on line 1 in the editor</p>
    )}
       <button
      onClick={() => {
        if (markers) markers.forEach((m) => m.setMap(null)) // tell google to remove
        setMarkers(undefined) // syncronize our react state to be empty
        skip.current = true // temporary condition to avoid re-creating markers, for this example only!
      }}>
      Remove markers!
    </button>
    <div style={{ width: '100vw', height: 500 }} ref={mapRef}></div>
 
    </Wrapper>
    )
}

Open on CodeSandbox

Google Maps Advanced Markers React

This is a draft – check back soon!

See demo.resider.com for a working example.

Understanding How Google and React share the DOM

  • In short, they don't!
  • Google asks React for a ref to a dom element.
  • Then, google takes the element and moves it! The audacity!
  • React does not like this. Because when React attempts to unmount the component containing the ref, the referenced element will be gone! And React will throw an error something like 'The node to be removed is not a child of this node."
  • It's clear what the problem is based on the error message, but only if you knew the google moves the element.
  • I only discovered this by adding an id to my referenced element and searching for it in the DOM before and after initializing an Advanced Marker. Sure enough, google had moved it.
  • The solution, luckily, is simple. To keep react happy, simply wrap your referenced element in another element, such as a div, or react fragment. E.g.
1const AdvancedMarker = () => {
2 const markerRefForGoogle = React.useRef<HTMLDivElement>(null)
3 
4 return (
5 <div id='stable'>
6 {' '}
7 // for react
8 <div ref={markerRefForGoogle}></div> // for google
9 </div>
10 )
11}

Now, when React tries to unmount this AdvancedMarker component, it will see this:

1<div id='stable'></div>

The div with the ref is gone! But, from React's perspective, the element you're trying to remove now is the div with id stable. It doesn't look any further down that tree, since everything it contains will be unmounted anyway, and checking any further would be less performant.

Next

Soon, I will add a full file showing how to add Advanced Markers in a react application.