google-map-react icon indicating copy to clipboard operation
google-map-react copied to clipboard

Warning: Google Maps already loaded outside @googlemaps/js-api-loader.

Open marnixhoh opened this issue 5 years ago • 19 comments

Describe the bug 🐛

I use @googlemaps/js-api-loader explicitly on a page where google-map-react is not used. However, when I navigate between this page and one that does use google-map-react (or vice-versa) I get the following warning:

Google Maps already loaded outside @googlemaps/js-api-loader. This may result in undesirable behavior as options and script parameters may not match.

I noticed that I can easily prevent this warning from showing by conditionally loading Google Maps on the page that explicitly uses @googlemaps/js-api-loader by checking to see if window.google exists or not (see minimal reproducible example below). However, I couldn't find a way to do something similar with google-map-react?

Any help or suggestions would be greatly appreciated!

To Reproduce 🔍 Minimal reproducible example:

File A.js (conditionally uses @googlemaps/js-api-loader):

import React, { useState, useEffect } from 'react'
import { Loader } from '@googlemaps/js-api-loader'
import Link from 'components/elements/Link'

const A = () => {
    const [isMapApiLoaded, setIsMapApiLoaded] = useState(false)

    useEffect(() => {
        if (isMapApiLoaded || window.google) return
        const loader = new Loader({
            apiKey: process.env.API_KEY,
            libraries: ['places']
        })
        loader.load().then(() => setIsMapApiLoaded(true))
    }, [isMapApiLoaded])
    return (
        <div>
            <h1>A</h1>
            <Link href="/b">b</Link>
        </div>
    )
}

export default A

File B.js (uses google-map-react)

import React from 'react'
import GoogleMapReact from 'google-map-react'
import Link from 'components/elements/Link'

const B = () => {
    const apiHasLoaded = (map, maps) => {
        console.log('Loaded!')
    }

    return (
        <div>
            <GoogleMapReact
                defaultZoom={10}
                bootstrapURLKeys={{
                    key: process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY,
                    libraries: ['places']
                }}
                yesIWantToUseGoogleMapApiInternals
                onGoogleApiLoaded={({ map, maps }) => apiHasLoaded(map, maps)}
                options={() => ({
                    fullscreenControl: false
                })}
            ></GoogleMapReact>
            <Link href="/a">a</Link>
        </div>
    )
}

export default B

Environment:

  • OS: mac
  • Browser: chrome
  • Version; @googlemaps/js-api-loader: 1.11.1, google-map-react: 2.1.9

marnixhoh avatar Mar 29 '21 12:03 marnixhoh

Also having this issue

karlkaspar avatar May 05 '21 11:05 karlkaspar

watching...we also use @react-google-maps/api for the useLoadScript feature:

const { isLoaded: isGoogleLoaded } = useLoadScript({
    googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY,
    libraries: LIBRARIES,
  });

Are both libs needed?

    "@react-google-maps/api": "^2.2.0",
    "google-map-react": "^2.1.9",

lucksp avatar Jun 03 '21 16:06 lucksp

Same issue here

viniciusueharaweb avatar Jun 13 '21 21:06 viniciusueharaweb

I'm also getting this warning because I'm loading the Google Maps API via a script tag in the header of every page of my application:

<script
    type="text/javascript"
    src={`https://maps.googleapis.com/maps/api/js?key=${process.env.GOOGLE_API_KEY!}&libraries=places`}
/>

I'm doing this because I also need the API for react-google-places-autocomplete. And, this script tag is one method they suggest for loading the API.

My problem is that I also need to use the onGoogleApiLoaded function to center the map once loaded, and it's not clear if that would work if I loaded the Google Maps API through the script tag instead of this library.

Does anyone actually know what issues this warning is alluding to? If I have two different components that are doing different things and only communicating to each other through state in my app, is there really a risk of things breaking by having both use different instances of the API? Or is it even two instances or one component overriding the version of the API the other component is using? If that's the case I would just need to make sure both components expect the same version of the Google Maps API and I can safely ignore this warning right? I have yet to notice any issues in my application with this warning but I don't know if I'm sitting on a ticking time bomb.

Also FYI, there's already been a lot of discussion about this issue in https://github.com/google-map-react/google-map-react/issues/954. The author says that this issue was fixed in 2.1.9 but it seems like a lot of people are still reporting the warning.

thallada avatar Jun 18 '21 22:06 thallada

related: https://github.com/googlemaps/js-api-loader/issues/307

jpoehnelt avatar Jun 22 '21 14:06 jpoehnelt

I got the same issue when using this lib with react-google-places-autocomplete

Here is my workaround to hide the warning but not recommend

useEffect(() => { if (typeof google !== 'object') { const result = useJsApiLoader({ googleMapsApiKey: COMMON_CONFIG.googleMapsApiKey, }); setIsMapLoaded(result.isLoaded); } else { setIsMapLoaded(true); } }, []);

phamvanhan68 avatar Jul 26 '21 15:07 phamvanhan68

@jpoehnelt Could you check on this bug? 🙏

itsmichaeldiego avatar Aug 01 '21 21:08 itsmichaeldiego

I haven't seen anything showing a bug here. It seems that most of these issues except maybe the first comment are all mixing different methods of loading the API, leading to the warning. Perhaps the first comment could be related to how the application is bundled and and not using the singleton??

jpoehnelt avatar Aug 02 '21 14:08 jpoehnelt

I have resolved it by using a single API now. We migrated all to react-google-maps-api

lucksp avatar Aug 02 '21 14:08 lucksp

EDITED:

I realized this question is asking specifically about the js-api-loader. In their docs it states you can pass id prop which will prevent double loading the script. The below script will only load google maps if it was not previously loaded

const loader = new Loader({
  apiKey: "",
  id: "__googleMapsScriptId",
  version: "weekly",
  libraries: ["places"]
});

I had the same issue, what I ended up doing is dynamically loading the script when the page is mounted. The below functions will check if the script is loaded and only load it if it doesn't already exist.

function setupCallback(script: any, callback: () => void) {
  if (script.readyState) {
    script.onreadystatechange = function () {
      if (script.readyState === 'loaded' || script.readyState === 'complete') {
        script.onreadystatechange = null
        callback()
      }
    }
  } else {
    script.onload = () => {
      callback()
    }
  }
}

export const loadScript = (url: string, callback: () => void, id: string) => {
  let script: any
  if (document.getElementById(id)) {
    // If the script already exists then add the new callback to the existing one
    script = document.getElementById(id)
    const oldFunc = script.onload
    setupCallback(script, () => {
      oldFunc && oldFunc()
      callback()
    })
  } else {
    // If the script doesn't exists then create it
    script = document.createElement('script')
    script.type = 'text/javascript'
    script.src = url
    script.setAttribute('id', id)
    setupCallback(script, callback)
    document.getElementsByTagName('head')[0].appendChild(script)
  }
}

Then it can be called with

    loadScript(
      `https://maps.googleapis.com/maps/api/js?key=API_KEY&libraries=places`,
      callbackFunction,
      '__googleMapsScriptId',
    )

khevamann avatar Aug 12 '21 00:08 khevamann

@khevamann Could you submit a PR in our library so we could handle this? Seems that @jpoehnelt's fix was missing that.

itsmichaeldiego avatar Aug 18 '21 20:08 itsmichaeldiego

@itsmichaeldiego I looked into it and it looks like it already works as is. All additional props from bootstrapURLKeys are automatically passed into the loader, and js-api-loader accepts an id like I mentioned above. So the code below works correctly. I put in a PR to update this in the types definition. Is there somewhere I should add this to documentation.

<GoogleMapReact
        bootstrapURLKeys={{ key: API_KEY, libraries: ['places'], id: 'CUSTOM_SCRIPT_ID' }}
        defaultCenter={defaultProps.center}
        defaultZoom={defaultProps.zoom}
      />

Then to use google maps script anywhere else on the page, you can use

const loader = new Loader({
  apiKey: "",
  id: "__googleMapsScriptId",
  version: "weekly",
  libraries: ["places"]
});

khevamann avatar Aug 18 '21 21:08 khevamann

Perfect @khevamann, thank you so much!

itsmichaeldiego avatar Aug 19 '21 01:08 itsmichaeldiego

Is it just me or is this fix gone again? I have attached a screenshot of the type file... and I've got version 2.1.10

image

dillonharless18 avatar Mar 10 '22 06:03 dillonharless18

@dillonharless18 Please share you app code. You likely have the Google Maps API loaded outside of this component library.

jpoehnelt avatar Mar 10 '22 16:03 jpoehnelt

EDITED:

Non-issue... messed up my center property. Still strange that I don't see ID in the type file for google-map-react, but it seems to be working. Let me know if I should just delete these comments @jpoehnelt


Thanks for the quick response @jpoehnelt

Yeah I loaded it once before using @googlemaps/js-api-loader... I might not understand it well enough, but I wouldn't think that should mess with the type files for google-map-react.

This happens first. It's imported and run by a file early in the business logic to geocode an address.

import { Loader } from '@googlemaps/js-api-loader';

const loadScript = async () => {
  const loader = new Loader({
    apiKey: process.env.REACT_APP_GOOGLE_MAPS_API_KEY
    // version: 'beta',
    id: "__googleMapsScriptId",
  });

  await loader.load();
};

export const geo = async(address: string) => {
  try {
    await loadScript();
    const geocoder = new google.maps.Geocoder();
    const results = await geocoder.geocode({ address: address });
    return results
  } catch (e) {
    console.error(`There was an error: ${JSON.stringify(e, null, 2)}`)
  }
}

export async function reverseGeo(
  lat: number, long: number
): Promise<string> {
  await loadScript();
  const geocoder = new google.maps.Geocoder();

  const latlng = {
    lat: lat,
    lng: long,
  };

  try {
    const { results } = await geocoder.geocode({ location: latlng });
    if (results && results[0]) {
      return results[0].formatted_address;
    }
  } catch (e) {
    // handle exception
  }

  throw new TypeError('Zero result or reverse geo Failed');
}

I then try to render a map later on in the app using this file

import React, { useContext, useRef, useEffect, useState } from 'react';
import GoogleMapReact from 'google-map-react';
import { useTranslation } from 'react-i18next';
import { DemographicsContext } from '../context/DemographicsContext';
import { ReactComponent as CC_YouMapMarker } from '../svg/CC_YouMapMarker.svg';
import { ReactComponent as CC_PODMapMarker } from '../svg/CC_PODMapMarker.svg';
import { DisasterContext } from '../context/DisasterContext';
import { StyledExplcitGrid, StyledPageBodyText } from './ReusableStyledComponents';




interface IPOD {
  OrganizationName: string,
  PODID: string,
  PODLatitude: string,
  PODLongitude: string,
  PODDescription: string
}

interface IMapMarker {
  lat: number,
  lng: number,
  type: string,
  id: string,
  popupCoords?: {lat: number, lng: number},
  handleMarkerClick: Function,
  markerState: any,
  POD?: IPOD
}

const MapMarker: React.FC<IMapMarker>  = ({ type, id, handleMarkerClick, POD }) => {

  const { t, i18n } = useTranslation();
  
  const onMarkerClick = (e) => {
    if ( type === "POD" ) {
      e.preventDefault();
      handleMarkerClick(e, null, "POD", POD);
    } else {
      e.preventDefault();
      handleMarkerClick(e, null, "USER");
    }
  }

  return (
      <StyledExplcitGrid
        id={id}
        width={'fit-content'}
        onClick={(e) => onMarkerClick(e)}
      >
        { 
          type === 'USER' &&
          <>
            <CC_YouMapMarker pointerEvents="none"/>
            <StyledPageBodyText fontSize={"1em"} fontWeight={"800"}>{t("YOU")}</StyledPageBodyText>
          </>
        }
        {
          type === 'POD' &&
          <>
            <CC_PODMapMarker pointerEvents="none" />
            <StyledPageBodyText fontSize={"1em"} fontWeight={"800"}>{POD?.PODDescription}</StyledPageBodyText>
          </> 
        }
    </StyledExplcitGrid>
  )
}



interface ISimpleMap {
  apiKey: string,
  center?: any,
  zoom?: number,
  handleMarkerClick: Function,
  markerState: any,
  PODS?: any
}


const SimpleMap: React.FC<ISimpleMap> = (props: any) => {
  
  const { handleMarkerClick, markerState, PODS } = props;

  const demographicsContext = useContext(DemographicsContext);
  const { homeLatLng } = demographicsContext;
  const disasterContext = useContext(DisasterContext);

  const apiKey = props.apiKey ? props.apiKey : "KEY NOT FOUND IN SIMPLE MAP";
  const podType: string = "POD";

  let center = {...homeLatLng};
  let zoom   = props.zoom ? props.zoom : 10

  

  
  return (
    <>    
     <div style={{ height: '45vh', width: '100%' }}> 
        <GoogleMapReact
          options={{
            styles: [
              {
                "elementType": "labels",
                "stylers": [
                  {
                    "visibility": "off"
                  }
                ]
              },
              {
                "featureType": "administrative.locality",
                "stylers": [
                  {
                    "visibility": "on"
                  }
                ]
              },
              {
                "featureType": "administrative.neighborhood",
                "stylers": [
                  {
                    "visibility": "on"
                  }
                ]
              },
              {
                "featureType": "administrative.province",
                "stylers": [
                  {
                    "visibility": "on"
                  }
                ]
              },
              {
                "featureType": "road",
                "stylers": [
                  {
                    "visibility": "on"
                  }
                ]
              }
            ]
          }}
          
          bootstrapURLKeys={{ key: apiKey }}
          center={center}
          zoom={zoom}
          yesIWantToUseGoogleMapApiInternals
          // onGoogleApiLoaded={initGeocoder}
        >
          {
            PODS.map((pod, i) => {
              return(
                <MapMarker
                  key={pod.PODID.concat(`-key-${i}`)}
                  lat={pod.PODLatitude}
                  lng={pod.PODLongitude}
                  type={podType}
                  id={pod.PODID.concat(`-${i}`)}
                  // popupCoords={pod.center}
                  handleMarkerClick={handleMarkerClick}
                  markerState={markerState}
                  POD={{...pod}}
                />
              )
            })
          }
          <MapMarker
            lat={center.lat}
            lng={center.lng}
            type="USER"
            // onMarkerClick={handleMarkerClick}
            id={"USER"}
            handleMarkerClick={handleMarkerClick}
            markerState={markerState}
          >
          </MapMarker>
        </GoogleMapReact>
    </div>
    </>
  );
}

export default SimpleMap;

Problem is the map is blank... but the markers show. I put a screenshot below. The map was showing just fine before I started using the geocoding API earlier in the app flow. Fwiw when I first introduced importing the geocoding API, I was getting an error on the map screen that said something like "can't import API a second time with different parameters" so I resolved the difference in the parameters when google-map-react loads the API and the error went away, but left me with the blank map.

image

dillonharless18 avatar Mar 10 '22 21:03 dillonharless18

Im having a similar error as the above comment. Using PlacesAutocomplete which has it's own desires for loading things and seems to be conflicting with google-map-react.

Like the above poster, my markers appear but not the map.

Snarik avatar Nov 17 '22 02:11 Snarik

@Snarik I also have a same problem, whichever load first, it will work. If I open PlaceAutocomplete first. then its work fine. then google-map-react not work. if I load google-map-react, then PlaceAutoComplete not work.

did you find any solution to this.

sgarchavada avatar Oct 03 '23 06:10 sgarchavada

Solution Found

Do not add key here

<GooglePlacesAutocomplete
      apiKey={process.env.REACT_APP_GOOGLE_MAP_KEY}    // do not add this line at all.
    />

instead add script in public/index.html file

Make sure to add Library=places in the script

This way whenever you open the placepicker page, it will not try to initialise api key again. bcz its already loaded.

sgarchavada avatar Oct 03 '23 06:10 sgarchavada