google-map-react / google-map-react

Google map library for react that allows rendering components as markers :tada:
http://google-map-react.github.io/google-map-react/map/main/
MIT License
6.38k stars 835 forks source link

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

Open marnixhoh opened 3 years ago

marnixhoh commented 3 years ago

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:

karlkaspar commented 3 years ago

Also having this issue

lucksp commented 3 years ago

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",
viniciusueharaweb commented 3 years ago

Same issue here

thallada commented 3 years ago

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.

jpoehnelt commented 3 years ago

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

phamvanhan68 commented 3 years ago

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); } }, []);

itsmichaeldiego commented 3 years ago

@jpoehnelt Could you check on this bug? πŸ™

jpoehnelt commented 3 years ago

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??

lucksp commented 3 years ago

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

khevamann commented 3 years ago

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',
    )
itsmichaeldiego commented 3 years ago

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

khevamann commented 3 years ago

@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"]
});
itsmichaeldiego commented 3 years ago

Perfect @khevamann, thank you so much!

dillonharless18 commented 2 years ago

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

jpoehnelt commented 2 years ago

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

dillonharless18 commented 2 years ago

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

Snarik commented 1 year ago

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.

sgarchavada commented 11 months ago

@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 commented 11 months ago

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.