0

I have a Next.js page which shows a Map component using the Advanced Markers functionality documented here. I'm intermittently getting the following error when the page loads: InvalidValueError: initMap is not a function

If the user navigates away from the page and then returns, there's no error.

Because of the Advanced Markers functionality the script can't be imported as a package, so instead I'm using the Next.js Script tag. I use a useEffect to check that we have a window, and I'm also using setInterval to delay running the initMap code until (in theory) the script is loaded.

useEffect(() => {
   function initMap() {
       const interval = setInterval(() => {
  if (
    typeof window !== "undefined" &&
    window.google &&
    window.google.maps
  ) {
    clearInterval(interval);
    if (googlemap.current) {
      const map = new google.maps.Map(googlemap.current, {
        center: { lat: coords[0]?.lat, lng: coords[0]?.lng },
        zoom: 15,
        mapId: "d2a1a27e40cc216a",
      });

      const bounds = new google.maps.LatLngBounds();

      for (const coord of coords) {
        const advancedMarkerView =
          new google.maps.marker.AdvancedMarkerView({
            map,
            content: buildContent(coord),
            position: { lat: coord?.lat, lng: coord?.lng },
            zIndex: 1,
          });
        const element = advancedMarkerView.element;
        bounds.extend(advancedMarkerView.position);
      }
      map.fitBounds(bounds);
    }
  }
}, 1000);
}
initMap();
}, []);
return (
    <>
        <Script src='https://maps.googleapis.com/maps/api/js?key=API_KEY&v=beta&libraries=marker&callback=initMap' />
        <div ref={googlemap} className='h-full w-full'></div>
    </>
)

Can anyone see what I' doing wrong? What's the correct, rock-solid way of implementing a Google map in Next.js using the Javascript API and Advanced Markers functionality, please?

Albina
  • 1,901
  • 3
  • 7
  • 19
Ray Purchase
  • 681
  • 1
  • 11
  • 37
  • 2
    Good username :) Try defining your `initMap` function as a window object property. Something like `window.initMap = initMap;`. There are a number of related questions here. Please check. – MrUpsidown Mar 20 '23 at 08:26
  • 1
    @MrUpsidown Cheers! That seems to have worked. Will happily mark that as correct if you add it as an answer. Much appreciated – Ray Purchase Mar 23 '23 at 13:12

1 Answers1

1

According to the documentation, loading the API should be done this way:

The defer attribute causes the callback to execute after the full HTML document has been parsed. For non-blocking uses, avoiding race conditions, and consistent behavior across browsers, consider loading using Promises with https://www.npmjs.com/package/@googlemaps/js-api-loader.

Loading the script without the defer (or async) attribute often leads to an error:

Uncaught (in promise) - initMap is not a function

function initMap() {
  // Do nothing
}
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap"></script>

Adding defer (or async) to the HTML script tag gets rid of this error.

function initMap() {
  // Do nothing
}
<script defer src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap"></script>

Other ways and instructions on how to load the API can be found here.

Most code examples in the documentation also mention that you should attach the callback function to the window object:

// Attach your callback function to the `window` object
window.initMap = function() {
  // JS API is loaded and available
};

Or an equivalent:

function initMap() {
  // Your code here
}

window.initMap = initMap;

Using asnyc / defer attributes is your own choice and will often depend on how your website / app is configured and loads the various scripts and/or APIs you will need. To understand the main differences, you can refer to this post.

MrUpsidown
  • 21,592
  • 15
  • 77
  • 131