GithubHelp home page GithubHelp logo

visgl / react-google-maps Goto Github PK

View Code? Open in Web Editor NEW
551.0 551.0 46.0 3.81 MB

React component and hooks library for Google Maps

Home Page: https://visgl.github.io/react-google-maps/

License: MIT License

JavaScript 15.49% TypeScript 81.58% MDX 1.08% CSS 1.26% Shell 0.59%

react-google-maps's People

Contributors

achhunna avatar aemkei avatar amuramoto avatar arielbenichou avatar benschlegel avatar chestermjr avatar dancielos avatar dependabot[bot] avatar freundschaft avatar github-actions[bot] avatar jezmck avatar jlburkhead avatar laotoutou avatar leighhalliday avatar maciej-ka avatar mauriciorobayo avatar mrmetalwood avatar osmanonurcan avatar plumdumpling avatar rodrigofariow avatar shuuji3 avatar thekip avatar trustdec avatar usefulthink avatar wanderingbrooks avatar wangela avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-google-maps's Issues

How to update the map after a change in state?

Hello!

I'm testing the library and wanted to add custom Zoom-in Zoom-out buttons, but the map does not update when the zoom values updates.

I created a simple GoogleMap componet like this:

"use client";

import { APIProvider, Map } from "@vis.gl/react-google-maps";

export default function GoogleMap({ zoom }: { readonly zoom: number }) {
  const position = { lat: 53.54, lng: 10 };

  return (
    <APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? ""}>
      <div className="aspect-video">
        <Map zoom={zoom} center={position} />
      </div>
    </APIProvider>
  );
}

and then used it on another component like this:

"use client";

import { useState } from "react";
import GoogleMap from "./GoogleMap";

export default function Home() {
  const [zoom, setZoom] = useState(10);
  return (
    <>
      <GoogleMap zoom={zoom} />
      <button
        onClick={() => setZoom(zoom + 1)}
      >
        Zoom in
      </button>
      <button
        onClick={() => setZoom(zoom - 1)}
      >
        Zoom out
      </button>
      <div>Zoom: {zoom}</div>
    </>
  );
}

When I change the zoom using the parent compent the child component does not update to reflect the new zoom value.

Here is a test repository: https://github.com/MauricioRobayo/google-maps-test

Map component not applying MapStyles passed into `styles` prop

Hello,

I am trying to turn off the Points of Interest (POI) on the Map component, but there's an issue with the prop not being applied. Here is my code snippet:

import React, { useState } from 'react';
import { APIProvider, Map, AdvancedMarker, Pin, InfoWindow } from '@vis.gl/react-google-maps';

 <APIProvider apiKey={API_KEY}>
        <Map
          onLoadMap={onLoad}
          zoom={zoom}
          center={center}
          disableDefaultUI
          style={{height: '100%', width: '100%'}}
         // issue is with the below prop - not being applied
          styles={[
            {
              featureType: 'poi',
              elementType: 'labels',
              stylers: [{ visibility: 'off' }],
            },
          ]}
        >
    <AdvancedMarker
      position={{ lat: latitude, lng: longitude }}
      key={key}
      title={title}
    >
       <Pin />
    </AdvancedMarker>
    <InfoWindow>{Text}</InfoWindow>
</Map>
</APIProvider>

I've tried variations of the styles prop as well. As in, I've tried:

            {
              featureType: 'poi',
              elementType: 'labels',
              stylers: [{ visibility: 'off' }],
            },
            {
              featureType: 'poi.business',
              elementType: 'labels',
              stylers: [{ visibility: 'off' }],
            },
            {
              featureType: 'poi.business',
              stylers: [{ visibility: 'off' }],
            },
            {
              featureType: 'poi.business',
              elementType: 'all',
              stylers: [{ visibility: 'off' }],
            },

None of these result in turning of the poi visibility on the map as far as I can see. I've double checked the Google Map docs as well to make sure of syntax/format: https://developers.google.com/maps/documentation/javascript/style-reference

For clarification, I would like to "turn off" all of these gray colored markers, and only have the black Marker (with the 4) showing on the map.

Screenshot 2023-11-09 at 2 42 37 PM

[Bug] Multiple advanced markers with popups renders markers underneath the popup even with a higher z-index

Description

I actually tried using AdvancedMarker to render a custom popup box that displays some information.
But whenever I do that while having multiple Markers on the map, the Popup div overlaps some of the Markers and doesn't overlap over the others.

I investigated a little further and noticed that the AdvancedMarker is adding z-index to the markers in an increasing fashion which I believe is causing my Popup div to overlap some and not the others.

I could definitely be wrong in my investigation; if so, any help would be appreciated on the same.
I'll add a code sandbox here: https://codesandbox.io/p/sandbox/busy-ptolemy-wwp52m

I did dig in a little more and found that the markers are being added with a class called -marker-view, which have a transform style on them, because of which I believe the z-index gets to a different stacking context so a global css style doesn't work on it even with !important flag and only an inline css style will do the job, which I don't think is possible as of now.

Steps to Reproduce

https://codesandbox.io/p/sandbox/busy-ptolemy-wwp52m

Environment

  • Library version: latest
  • Google maps version: weekly
  • Browser and Version: Chrome
  • OS: Windows 11

Logs

No response

Module not found: Error: Can't resolve '@vis.gl/react-google-maps'

Description

The new release 0.6.1 brought a new issue to the table as described in the title.

Steps to Reproduce

Upgraded from 0.6.0 to 0.6.1

Environment

  • Library version: 0.6.0
  • Google maps version: weekly
  • Browser and Version: Chrome Version 121.0.6167.139 (Official Build) (arm64)
  • OS: Apple M1 Max Ventura 13.2.1 (22D68)

Logs

Failed to compile.

Module not found: Error: Can't resolve '@vis.gl/react-google-maps' in '/Users/dashboard-ui/src/pages/cars/components'

ERROR in ./src/pages/cars/components/MapTracker.js 5:0-77
Module not found: Error: Can't resolve '@vis.gl/react-google-maps' in '/Users/dashboard-ui/src/pages/cars/components'

How do i detect zoom changes?

Is there any event that I can use to detect zoom changes?
I couldn’t find anything in the docs and type.d.ts file.

[Feat] Load Google Maps API Async

Target Use Case

When you load a map you get the following warning in the console:

Google Maps JavaScript API has been loaded directly without loading=async.

Proposal

Use @googlemaps/react-wrapper instead of custom wrapper

AdvancedMarker draggable doesn't work

Using Next.js 13.5.6,
@vis.gl/react-google-maps: 0.1.2

<APIProvider apiKey={API_KEY} libraries={['marker']}>
      <Map
        mapId={'DEMO_MAP_ID'}
        zoom={10}
        center={{lat: 0, lng: 0}}
        gestureHandling={'greedy'}
        disableDefaultUI={true}>
        <AdvancedMarker
          position={{lat: 20, lng: 10}}
          draggable={true}>
        </AdvancedMarker>
      </Map>
</APIProvider>

[Bug] Defining the optional autocomplete options caused a useEffect error

Description

When using the Autocomplete hook and trying to set extra options I get a console error:

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

I used the example code from the hook reference docs.

Steps to Reproduce

Using Next.js w/ Typescript

With-in a component I copied the code from the hook docs page but added:

useAutocomplete({
        inputField: (inputRef && inputRef.current),
        options: {
            strictBounds: false,
        },
        onPlaceChanged
    });

removing the 'options' field resolves the useEffect error. I have no useEffect hook within this component..

Environment

  • Library version: @vis.gl/react-google-maps": "^0.4.1"
  • Google maps version: weekly
  • Browser and Version: Chrome (119.0.6045.199)
  • OS: OSX - 14.1.2 (23B92)

Logs

app-index.js:32 Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
    at DestinationCard (webpack-internal:///(app-pages-browser)/./app/ui/destination-card.tsx:18:11)
    at APIProvider (webpack-internal:///(app-pages-browser)/./node_modules/@vis.gl/react-google-maps/dist/index.modern.mjs:253:7)
    at div
    at CoreMap (webpack-internal:///(app-pages-browser)/./app/ui/map.tsx:37:74)
    at InnerLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:240:11)
    at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:72:9)
    at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:80:11)
    at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:54:9)
    at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:62:11)
    at LoadingBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:345:11)
    at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:130:11)
    at InnerScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:151:9)
    at ScrollAndFocusHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:226:11)
    at RenderFromTemplateContext (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/render-from-template-context.js:15:44)
    at OuterLayoutRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/layout-router.js:355:11)
    at body
    at html
    at RedirectErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:72:9)
    at RedirectBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/redirect-boundary.js:80:11)
    at NotFoundErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:54:9)
    at NotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/not-found-boundary.js:62:11)
    at DevRootNotFoundBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/dev-root-not-found-boundary.js:32:11)
    at ReactDevOverlay (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/internal/ReactDevOverlay.js:66:9)
    at HotReload (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/react-dev-overlay/hot-reloader-client.js:295:11)
    at Router (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:169:11)
    at ErrorBoundaryHandler (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:100:9)
    at ErrorBoundary (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/error-boundary.js:130:11)
    at AppRouter (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/components/app-router.js:451:13)
    at ServerRoot (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:128:11)
    at RSCComponent
    at Root (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/client/app-index.js:144:11)

[RFC] removing hooks from the library for the 1.0 release

In recent discussions we came to the conclusion that it would probably make sense to remove some of the hooks from the library for the first major release in order to limit the API surface and consolidate what we have before possibly reintroducing them if needed.

The hooks that are definitely going to stay:

  • useMap()
  • useApiIsLoaded() / useApiLoadingStatus()
  • useMapsLibrary() (with the pending change to return the library as returned by google.maps.importLibrary)

Trivial hooks that aren't really needed and will be removed (all of their use-cases can be replaced with a combination of useMapsLibrary() with useState() / useEffect()):

  • useAutocompleteService()
  • useDistanceMatrixService()
  • useElevationService()
  • useGeocodingService()
  • useMaxZoomService()
  • usePlacesService() (the only thing that differs from the other hooks here is that the places-service needs an element to render attribution information to, but that is something that is going to change in future versions of the google maps API).

Finally there are the more complex hooks. Considering those, I started to think that the approach to provide all the functionality of the Maps JavaScript API as is via react-hooks is not the best choice here and we should focus on providing useful abstractions solving specific common use-cases instead.

  • useAutocomplete():
    providing a good autocomplete solution is a lot more complicated than can properly be implemented in a simple hook. As such, there would probably only be a very limited number of users this implementation would actually be useful for. The current implementation of google.maps.places.Autocomplete is also problematic in a react-context due to it creating DOM Elements outside of the scope of the map or the autocomplete input-element. Given the new focus on Web Components in the Maps JavaScript API, it is also likely that google will provide a WC based implementation of the autocomplete input in the future that could possibly solve most of these issues and be used instead.

  • useDirectionsService(): this seems to fit well with the Idea to solve a common and specific use-case (rendering directions-results on the map). This should probably be refactored a bit to support a wider range of use-cases.

  • useStreetViewPanorama(): the StreetView functionality is a unique feature of the Google Maps API and definitely shouldn't be missing from this library. We should think about refactoring this to fit the common use-cases and possibly provide an alternative component-version to just render StreetView imagery.

Support for typescript types

Target Use Case

When developing with typescript. It can help with autocomplete and viewing the needed props.

Proposal

This feature will help make development in typescript easier.

How to use Geocoding library?

Hello, great package, thanks for the huge effort invested into this!

I was looking through the examples tyring to find something on how to use the Geocoding library but could not find anything.

Is there any recommended patter for this?

This is what I came up after exploring a bit, but not sure this is how it is intended to be used:

import {
  APIProvider,
  Map,
  useApiIsLoaded,
  useMapsLibrary,
} from "@vis.gl/react-google-maps";
import { useEffect, useState } from "react";

export default function GoogleMap() {
  const [address, setAddress] = useState("");
  return (
    <APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? ""}>
      <Geocoder address={address} />
      <label
        htmlFor="address"
        className="mb-2 text-sm font-medium text-gray-900"
      >
        Address:
      </label>
      <input
        id="address"
        type="text"
        className="max-w-sm  border border-gray-300 text-gray-900 rounded-lg p-2.5"
        onChange={(e) => setAddress(e.target.value)}
      />
    </APIProvider>
  );
}

function Geocoder({ address }: { readonly address: string }) {
  const [position, setPosition] = useState<google.maps.LatLng>();

  const geocoding = useMapsLibrary("geocoding");
  const isLoaded = useApiIsLoaded();

  useEffect(() => {
    async function getAddressPosition() {
      if (isLoaded && geocoding) {
        const geocoder = new geocoding.Geocoder();
        const response = await geocoder.geocode({ address });
        setPosition(response.results[0].geometry.location);
      }
    }

    getAddressPosition();
  }, [address, geocoding, isLoaded]);

  return (
    <div className="aspect-video max-w-lg">
      <Map
        key={address} // force a re-render while this lands: https://github.com/visgl/react-google-maps/pull/64
        zoom={9}
        center={position}
        disableDefaultUI
      />
    </div>
  );
}

[Bug] Using an invalid API key throws an error rendering making the page go blank

Description

If the project is using an invalid API key (for example, an unpaid expired one) the map is rendered for the first time with an error.
If a re-render occurs, the entire page will go blank and an error will be thrown inside useMapEvents:
TypeError: Cannot read properties of undefined (reading 'remove')
Here is where the error is thrown (I couldn't find the exact file inside the project itself):

function useMapEvents(map, cameraStateRef, props) {
  // note: calling a useEffect hook from within a loop is prohibited by the
  // rules of hooks, but it's ok here since it's unconditional and the number
  // and order of iterations is always strictly the same.
  // (see https://legacy.reactjs.org/docs/hooks-rules.html)
  for (const propName of eventPropNames) {
    // fixme: this cast is essentially a 'trust me, bro' for typescript, but
    //   a proper solution seems way too complicated right now
    const handler = props[propName];
    const eventType = propNameToEventType[propName];
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useEffect(() => {
      if (!map) return;
      if (!handler) return;
      const listener = map.addListener(eventType, ev => {
        const mapEvent = createMapEvent(eventType, map, ev);
        trackDispatchedEvent(mapEvent, cameraStateRef);
        handler(mapEvent);
      });
      return () => listener.remove();
    }, [map, cameraStateRef, eventType, handler]);
  }

it happens on return () => listener.remove(); inside the useEffect
a quick fix could be listener && listener.remove() for example, but I'm not sure of it's implications.

Steps to Reproduce

  1. Use an invalid API key
  2. The map will render with an error
  3. Try to re-render the page, an error will be thrown and the page will go blank

Environment

  • Library version: ^0.3.3
  • Google maps version: weekly
  • Browser and Version: Google Chrome 120.0.6099.234
  • OS: macOS

Logs

`TypeError: Cannot read properties of undefined (reading 'remove')`

Which happens here

function useMapEvents(map, cameraStateRef, props) {
  // note: calling a useEffect hook from within a loop is prohibited by the
  // rules of hooks, but it's ok here since it's unconditional and the number
  // and order of iterations is always strictly the same.
  // (see https://legacy.reactjs.org/docs/hooks-rules.html)
  for (const propName of eventPropNames) {
    // fixme: this cast is essentially a 'trust me, bro' for typescript, but
    //   a proper solution seems way too complicated right now
    const handler = props[propName];
    const eventType = propNameToEventType[propName];
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useEffect(() => {
      if (!map) return;
      if (!handler) return;
      const listener = map.addListener(eventType, ev => {
        const mapEvent = createMapEvent(eventType, map, ev);
        trackDispatchedEvent(mapEvent, cameraStateRef);
        handler(mapEvent);
      });
      return () => listener.remove();
    }, [map, cameraStateRef, eventType, handler]);
  }

specifically here:
return () => listener.remove(); inside the useEffect

[Bug] unable to zoom in, zoom out, or drag the map

Description

unable to zoom in, zoom out, or drag the map.

Steps to Reproduce

Whole Code

import {
  APIProvider,
  InfoWindow,
  Map,
  Marker,
  useMarkerRef,
} from "@vis.gl/react-google-maps";
import { useState } from "react";

interface Props {
  lat: number;
  lng: number;
  fullAddress: string;
}

export const GoogleMap = ({ lat, lng, fullAddress }: Props) => {
  const position = { lat, lng };

  const [markerRef, marker] = useMarkerRef();
  const [isInfoWindowOpen, setIsInfoWindowOpen] = useState(true);

  const toggleInfoWindowStatus = (state?: boolean) =>
    setIsInfoWindowOpen((previousState) => !previousState);

  return (
    <APIProvider
      apiKey={process?.env?.NEXT_PUBLIC_GOOGLE_MAP_API_KEY as string}
    >
      <Map
        center={position}
        zoom={14}
        className="h-60 w-full rounded-md border border-C_E1E1E1"
      >
        <Marker
          position={position}
          ref={markerRef}
          onClick={() => toggleInfoWindowStatus(true)}
        />
        {isInfoWindowOpen && (
          <InfoWindow
            anchor={marker}
            onCloseClick={() => toggleInfoWindowStatus(false)}
          >
            <span className="line-clamp-2 break-words">{fullAddress}</span>
          </InfoWindow>
        )}
      </Map>
    </APIProvider>
  );
};

Environment

  • Library version: @vis.gl/react-google-maps: 0.6.1
  • Browser and Version: Chrome
  • OS: Windows

Logs

NA

Module not found: Error: Can't resolve 'fast-deep-equal/react'

Description

Suddenly, Google maps were giving this error on my production application
image

Therefore, I thought of checking for latest updates of the map's library and found out that my current version was 0.5.0 and the latest one released was 0.6.0.
After upgrading the error in the console shown below is thrown and the app won't start.

Steps to Reproduce

This happened after upgrading @vis.gl/react-google-maps from 0.5.0 to 0.6.0

Environment

  • Library version: 0.6.0
  • Google maps version: weekly
  • Browser and Version: Chrome Version 121.0.6167.139 (Official Build) (arm64)
  • OS: Apple M1 Max Ventura 13.2.1 (22D68)

Logs

ERROR in ./node_modules/@vis.gl/react-google-maps/dist/index.modern.mjs 3:0-48
Module not found: Error: Can't resolve 'fast-deep-equal/react' in '/Users/ari/Work/JS/REACT/dashboard-ui/node_modules/@vis.gl/react-google-maps/dist'
Did you mean 'react.js'?
BREAKING CHANGE: The request 'fast-deep-equal/react' failed to resolve only because it was resolved as fully specified
(probably because the origin is strict EcmaScript Module, e. g. a module with javascript mimetype, a '*.mjs' file, or a '*.js' file where the package.json contains '"type": "module"').
The extension in the request is mandatory for it to be fully specified.
Add the extension to the request.

Could not find a declaration file for module '@vis.gl/react-google-maps'. v0.2.0

Hello!

I'm testing the package and installed the latest version at the moment (v0.2.0).

After installing the package and creating a component based on this example, I get the following typescript error:

Could not find a declaration file for module '@vis.gl/react-google-maps'. 

Downgrading to v0.1.2 seems to fix the issue.

Here is a test repo: https://github.com/MauricioRobayo/google-maps-test/blob/main/src/app/GoogleMap.tsx

Steps to reproduce:

  1. Clone the repo: https://github.com/MauricioRobayo/google-maps-test
  2. Install dependencies: npm install
  3. Build the app: npm run build

The build fails because of the error:

image

useMap always return null for me

Description

any ideas about why useMap is returning null instead of map context?

my map is working ok but i need no use useMap from react

Steps to Reproduce

...

Environment

...

Logs

No response

[Bug] Some ControlPositions are causing my component to disappear

Description

I'm working on a project and was trying to play around with the positions. Some of the positions work as expected, but when using the positions listed below the component disappears from my map. I've also tried using google.maps.ControlPosition and entering their respective constants, but that also didn't seem to work. TIA!

NOT WORKING

TOP_RIGHT;
RIGHT_TOP
RIGHT_CENTER
RIGHT_BOTTOM
BOTTOM_CENTER
BOTTOM_LEFT
BOTTOM_RIGHT

"use client";
import React from "react";
import {APIProvider, Map, Marker, ControlPosition, MapControl,} from "@vis.gl/react-google-maps";
import LocationList from "../LocationList/LocationList";

const GoogleMap = () => {
  const position = { lat: 40.69, lng: -74 };
  return (
    <APIProvider apiKey={process.env.NEXT_PUBLIC_MAPS_API_KEY}>
      <Map
        center={position}
        zoom={12}
        disableDefaultUI={true}
        zoomControl={true}
        options={{
          zoomControlOptions: { position: ControlPosition.TOP_LEFT }, // <---- Values also don't work here
        }}
        className="w-1/2 h-1/2"
      >
        <MapControl position={ControlPosition.BOTTOM_LEFT}>  // <------ Here
          <LocationList />
        </MapControl>
      </Map>
    </APIProvider>
  );
};

export default GoogleMap;

With ControlPosition.TOP_LEFT

image

With ControlPosition.TOP_RIGHT

image

Steps to Reproduce

Create a map component and use ControlPosition or google.maps.ControlPosition to try and set a position

Environment

  • Library version: "@vis.gl/react-google-maps": "^0.4.0"
  • Google maps version: weekly
  • Browser and Version: Google Chrome version 120.0.6099.72
  • OS: Ubunto/WSL2

Logs

N/A

[Feat] Allow `<Pin>` glyphs to be passed as children

Target Use Case

Currently, the only way pass a glyph thats an HTMLElement to a <Pin> element (as far as i can tell) is to create the element inline in a react component and pass that to the pin like in the following example:

export default function MyComponent() {
  const glyphIcon = document.createElement('img');
  glyphIcon.src = "/custom-icon.svg";

  return (
    <>
      <AdvancedMarker>
        <Pin glyph={glyphIcon}/>
      </AdvancedMarker>
    </>
  )
}

This also causes unexpected issues when trying to render multiple markers using Array.prototype.map(), where defining the element just once in the component will only render it out on the last AdvancedMarker, since the native DOM and reacts virtual DOM have some conflicts when using document.createElement(). The workaround is to move the document.createElement() part down into map, but still not a clean way to interact with react.

Instead, I think it would be much nicer if we could pass a glyph via react children, similar to how <MapControl> currently functions, which renders the children to appropriate HTMLElements to be used with the google maps api. As this is a react wrapper, removing the need to manually create DOM elements directly would be inline with what this library is trying to achieve.

This would increase code maintainability, decrease weird "non-react-like" code and decrease bug encounters when trying to interface with native DOM Elements and reacts virtual DOM when using this library (as well as just making the API easier to use).

Proposal

Ideally, when trying to pass a glyph thats an HTMLElement to a <Pin>, we could do something like this:

<AdvancedMarker>
  <Pin background={...}>
    <img src="/icon.svg" />
  </Pin>
</AdvancedMarker>

Where <Pin> interprets children that get passed as glyphs of type Element (according to types here) and gets rendered out appropriately.

[Bug] InfoWindow renders twice after content load

Description

When using the InfoWindow component it renders twice, once empty and then when the children components are rendered it refreshes.

before.mp4

Wasn't sure if it's relevant enough to create a pull request, but this fixed the problem:

/**
 * Props for the Info Window Component
 */
export type InfoWindowProps = google.maps.InfoWindowOptions & {
    onCloseClick?: () => void;
    anchor?: google.maps.Marker | google.maps.marker.AdvancedMarkerElement | null;
};

/**
 * Component to render a Google Maps Info Window
 */
export const InfoWindow = (props: PropsWithChildren<IMyInfoWindowProps>) => {
    const { children, anchor, onCloseClick, ...infoWindowOptions } = props;
    const map = useContext(GoogleMapsContext)?.map;

    const infoWindowRef = useRef<google.maps.InfoWindow | null>();

    const [contentContainer, setContentContainer] =
        useState<HTMLDivElement | null>(null);

    // create infowindow once map is available
    useEffect(() => {
        if (!map) return;

        if (!infoWindowRef.current) {
            infoWindowRef.current = new google.maps.InfoWindow(infoWindowOptions);
        }

        // Add content to info window
        const el = document.createElement('div');
        infoWindowRef.current.setContent(el);

        if (onCloseClick) {
            infoWindowRef.current.addListener('closeclick', () => {
                onCloseClick();
            });
        }

        setContentContainer(el);

        // Cleanup info window and event listeners on unmount
        return () => {
            if (infoWindowRef.current) {
                google.maps.event.clearInstanceListeners(infoWindowRef.current);
                infoWindowRef.current.close();
            }
            infoWindowRef.current = null;
            el.remove();

            setContentContainer(null);
        };
    }, [map, children, anchor]);

    // Open info window after content container is set
    useEffect(() => {
        if (contentContainer && infoWindowRef.current && anchor) {
            infoWindowRef.current.open({ map, anchor });
        }
    }, [contentContainer, infoWindowRef, anchor, map]);

    return (
        <>{contentContainer !== null && createPortal(children, contentContainer)}</>
    );
};
after.mp4

Steps to Reproduce

Just rendering the InfoWindow caused this issue, I will note that I used conditional rendering so the mounting functionality might be related to the issue:

<Map {...mapProps}>
    {anchor && <InfoWindow anchor={anchor} />
</Map>

Environment

  • Library version: @vis.gl/[email protected]
  • Google maps version: weekly
  • Browser and Version: Google Chrome Version 119.0.6045.160 (Official Build) (64-bit)
  • OS: Windows 10

Logs

No response

[Bug] Rendering many Advanced Markers as map children is extremely slow

Description

On initial component render, even a few dozen Advanced Markers as map children will take ~ 10 seconds to initially render map. A few hundred can crash the browser.

Interestingly, a workaround is to render 1 marker, then set timeout for > 100ms and render many. OR use legacy Marker component.

Steps to Reproduce

Render 100 AdvancedMarkers as map children (just with key, position props)

Environment

  • Library version: 0.5.0
  • Google maps version: weekly (3.55.8)
  • Browser and Version: Chrome 120
  • OS: Mac

Logs

No response

[RFC] should we add callbacks to the useMap and useMapsLibrary hooks?

An Idea for how to simplify the typical use-cases of the useMap and useMapsLibrary hooks just crossed my mind and I would love to hear what others think about this.

The most common use-case for the useMap hook follows this pattern:

function Component() {
    const map = useMap();

    useEffect(() => {
        if (!map) return;

        // do stuff with the map instance

        return () => {
            // cleanup might be needed
        };
    }, [map]);

    return ...;
}

This can be simplified if we move the useEffect into the useMap hook, allowing users to shorten the code to

 function Component() {
    useMap((map) => {
        // do something with the map instance
        
        return () => {
            // cleanup
        }
    });
 }

The biggest issue with this would be that we can't specify the dependencies for the effect in a clean way, so I guess an alternative would be to put this into a new hook useMapEffect() which would follow the structure of useEffect() and make the semantics of the dependencies array and the returned cleanup-function a bit clearer:

function Component() {
    useMapEffect((map) => {
      // do stuff with map

      return () => { 
        // cleanup
      };
    }, [some, additional, dependencies]);
}

Something similar can be done for the typical use-case of useMapsLibrary:

function Component() {
    const [service, setService] = useState(null);
    const placesLib = useMapsLibrary('places');
    
    useEffect(() => {
        if (!placesLib) return;

        setService(new placesLib.PlacesService());
    }, placesLib);
}

Since the cleanup-function will typically not be needed with useMapsLibrary, we could go even one step further and pass through the value returned by the callback, so the example above would become:

function Component() {
    const placesService = useMapsLibrary('places', 
      placesLib => new placesLib.PlacesService()
    );
}

what do you think about these? Is that too far from what react usually does or could those be useful?

onClick event doesn't work on Map component

When I tried to get the coordinates with a onClick event in the map it doesn't show nothing. How to obtain the coordinates?

For example, this doesn't work:

 <Map
        onClick={(event) => { console.log(event) }}
        style={{ width: '400px', height: '400px' }}
        zoom={5}
        center={{
            lat: -17.146,
            lng: -65.841
        }}
    >
        <Marker position={{ lat: values.ubicacion.lat, lng: values.ubicacion.lng }} />
</Map>

[Bug] Race Condition during event handlers initialization

Description

Due to how this library binds event handlers to the google maps object there is a race condition. Events might be fired before react event handler is attached to the map. The simplest example is onProjectionChanged:

Steps to Reproduce

const App = () => {
  const onProjectionChanged = useCallback(function onProjectionChanged() {
    console.log("onProjectionChanged");
  }, []);

  const onBoundsChanged = () => {
    console.log("onBoundsChanged");
  };

  console.log("render app");
  return (
    <APIProvider apiKey={API_KEY}>
      <Map
        zoom={3}
        center={{ lat: 22.54992, lng: 0 }}
        gestureHandling={"greedy"}
        disableDefaultUI={true}
        onProjectionChanged={onProjectionChanged}
        onBoundsChanged={onBoundsChanged}
        onClick={() => console.log("click")}
      />
      <ControlPanel />
    </APIProvider>
  );
};

Try to reload map few times, and onProjectionChanged called not every time.

This happened due to map instance created in one useEffect and events bound in another. They are executed in diffrent async cycles, so map sometimes managed to fire an event before handler attached.

PS I tried to create a codesandbox with an issue, but in the sandbox no events were fired at all https://codesandbox.io/p/devbox/amazing-bohr-v65hqw

Environment

  • Library version: 0.4.1
  • Google maps version: 3.55.4
  • Browser and Version: Chrome 120
  • OS: MacOs Sonoma 14.1.2

Logs

No response

[Bug] You have included the Google Maps JavaScript API multiple times on this page. This may cause unexpected errors

Description

i am using useGoogleMapsScript for the auto complete places api on the same page. this cause issues with with the map.

can we check if the script has already been loaded to avoid this error?

Steps to Reproduce

export function GoogleMapsScript({ googleMapsApiKey, children }: LoadScriptProps) {
  const { isLoaded, loadError } = useGoogleMapsScript({
    googleMapsApiKey: googleMapsApiKey!,
    libraries,
  })

 <APIProvider apiKey={env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}>
</APIProvider>

conflicts...

Environment

  • Library version: latest
  • Google maps version: weekly
  • Browser and Version: chrome
  • OS:mac

Logs

No response

[Feat] Implement `<Polyline>` component.

Target Use Case

Have a react component for google.maps.Polyline similar to <Marker>/the other components.

Is this something this project plans on doing? I'm asking because I was planning on making a PR that implements this, but wanted to ask if this is in the scope of this project before putting in work into the PR.

Proposal

<Polyline {...props />

Passing zoom to AdvancedMarker casuing the map to render painfully slow

I want to show a custom marker only when the zoom is less than a particular value.
(I'm unsure if I'm following the right practice here).
When I try to zoom in or out the map zooms very very slowly.

Here is my code and here the stackblitz

export const App = () => {
  const [zoom, setZoom] = useState(3);

  const handleZoomChange = (event: any) => {
    setZoom(event?.detail?.zoom);
  };
  return (
    <div style={{ height: '100vh', width: '100vw' }}>
      <APIProvider apiKey={API_KEY}>
        <Map
          zoom={3}
          center={{ lat: 22.54992, lng: 0 }}
          gestureHandling={'greedy'}
          disableDefaultUI={true}
          onZoomChanged={handleZoomChange}
          mapId={MAP_ID}
        >
          {zoom < 4 && (
            <AdvancedMarker
              position={{
                lat: 53.58675649147477,
                lng: 10.045572975464376,
              }}
            >
              <div
                style={{
                  backgroundColor: 'red',
                  padding: '2rem',
                }}
              >
                custom marker
              </div>
            </AdvancedMarker>
          )}
        </Map>
      </APIProvider>
    </div>
  );
};

Please Note the normal Marker component doesn't have this issue.

[Bug] Adding more than one class to AdvancedMarkers className throws an error

Description

AdvancedMarkers throws an error if used with children and more than one class is added to the className prop.

I believe this is happening because underneath classList.add is used:

if (className) el.classList.add(className);

Steps to Reproduce

Try the following code:

import { APIProvider, AdvancedMarker, Map } from "@vis.gl/react-google-maps";

const apiKey = process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? "";
const mapId = process.env.NEXT_PUBLIC_GOOGLE_MAPS_MAP_ID ?? "";

export default function Page() {
  const position = { lat: 53.54992, lng: 10.00678 };
  return (
    <div className="h-screen w-full">
      <APIProvider apiKey={apiKey}>
        <Map zoom={14} center={position} mapId={mapId}>
          <AdvancedMarker 
            className="class1 class2" // -> This throws an error
            position={position}
          > 
            <h2>I am so customized</h2>
            <p>That is pretty awesome!</p>
          </AdvancedMarker>
        </Map>
      </APIProvider>
    </div>
  );
}

The error thrown is:

InvalidCharacterError: Failed to execute 'add' on 'DOMTokenList': The token provided ('class1 class2') contains HTML space characters, which are not valid in tokens.
image

Environment

  • Library version: 0.4.2
  • Google maps version: weekly
  • Browser and Version: Google Chrome v120.0.6099.129
  • OS: Mac

Logs

react-dom.development.js:20665 Uncaught DOMException: Failed to execute 'add' on 'DOMTokenList': The token provided ('class1 class2') contains HTML space characters, which are not valid in tokens.
    at eval (webpack-internal:///(app-pages-browser)/./node_modules/@vis.gl/react-google-maps/dist/index.modern.mjs:723:35)
    at commitHookEffectListMount (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:20998:23)
    at commitHookPassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23051:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23156:11)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23153:9)
    at recursivelyTraversePassiveMountEffects (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23134:7)
    at commitPassiveMountOnFiber (webpack-internal:///(app-pages-browser)/./node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js:23267:9)

[Bug] Issue Template Test

Description

Description of the issue

Steps to Reproduce

  • do this
  • do that
  • done

Environment

  • Library version:
  • Google maps version: weekly
  • Browser and Version:
  • OS:

Logs

jklasd 
asfjkladf
adf
adf
asdfsad
fdsaf
sa
fdas

[Bug] changing the center coordinates doesn't reposition the map

Description

Changing the coordinates by selecting different place doesn't reposition the map. new markers are added but the position of map stays as is.

Steps to Reproduce

  const [zoomLevel, setZoomLevel] = useState(12);
  const map = useMap();
  const [coords] = useState<Coordinates>(selectedCoords);

 return ( <APIProvider apiKey={env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY}>
      <div style={{ height: "90vh", width: "100%" }}>
        <Map
          center={coords}
          zoom={zoomLevel}
          disableDefaultUI={false}
          clickableIcons={true}
          mapId={process.env.NEXT_PUBLIC_MAP_ID}
        />
    </div>
)

Environment

  • Library version:
  • Google maps version: weekly
  • Browser and Version: chrome
  • OS: mac

Logs

No response

[Feat] `useMap` should log an error when used outside the APIProvider context

Target Use Case

A common mistake that is being made is to put the useMap hook into the component that renders the APIProvider, where the value of the provider can't be seen and the useMap hook will always return null.
This can be easily detected and prevented.

Proposal

In useMap, we should be able to detect if the APIProviderContext exists even if it hasn't been initialised yet and log an error if the context isn't available.

[Bug] Invalid/Expired API key crashes the page

Description

If the API key is expired/wrong. Then the page crashes once we move away from the map page. It throws the following error

Cannot read properties of undefined (reading 'remove')

Steps to Reproduce

https://codesandbox.io/p/sandbox/gracious-rain-slhtvx

  1. Open the above sandbox and click on "Map" link
  2. Map page will open and It will show "Oops! Something went wrong." as the API key is wrong.
  3. Click on Home link to go back to home page
  4. Click on Map again

Now after this If you move again to home page or any other route, It'll crash the page.

Environment

  • Library version: 0.4.1
  • Google maps version: weekly
  • Browser and Version: Chrome 120
  • OS: Windows 11

Logs

No response

Change `useMapsLibrary` to return the library objects instead of a boolean

In order to be closer to the implementation in the google maps API, we want to change the returned type for the useMapsLibrary hook from boolean to the actual API Object returned by google.maps.importLibrary. At the same time we want to drop support for importing multiple libraries at once.

This removes the requirement to access the maps API via the global google.maps. variables (which seems to be something google wants to move away from in the long term).

So instead of

const libraryLoaded = useMapsLibrary('places');

useEffect(() =>{
  if (!libraryLoaded) return;

  const svc = new google.maps.places.PlacesService();
  // ...
}, [libraryLoaded]);

it would be

const placesLibrary = useMapsLibrary('places');

useEffect(() =>{
  if (!placesLibrary) return;

  const svc = new placesLibrary.PlacesService();
  // ...
}, [placesLibrary]);

[Bug] Map will move when executing onClick function of Marker component

Description

I have a map with a list of markers. These markers have a custom icon as a symbol. When I press these markers, the map will move a few pixels to the top. It will also happen if you use normal markers. Please see the video's.

Schermopname.2023-12-04.om.15.46.05.mov
Schermopname.2023-12-04.om.15.54.41.mov

Steps to Reproduce

Using the following code

export default function PickupPointMaps({ pickupPoints, onClickMarker }: Props) {
  const map = useMap('pickup-points-map');

  useEffect(() => {
    if (pickupPoints) {
      const bounds = new google.maps.LatLngBounds();

      pickupPoints.forEach(({ pickupPoint }) => {
        bounds.extend({
          lat: pickupPoint.geoCoordinates.latitude,
          lng: pickupPoint.geoCoordinates.longitude,
        });
      });

      if (map) {
        map.fitBounds(bounds);
      }
    }
  }, [pickupPoints, map]);

  return (
    <Map
      id="pickup-points-map"
      zoom={12}
      center={map?.getBounds()?.getCenter()}
      gestureHandling="greedy"
      disableDefaultUI={true}
      zoomControl={true}
      styles={mapStyling}
    >
      {pickupPoints && pickupPoints.map(({ pickupPoint }) => (
        <Marker
          key={pickupPoint.id}
          position={{
            lat: pickupPoint.geoCoordinates.latitude,
            lng: pickupPoint.geoCoordinates.longitude,
          }}
          icon={{
            path: 'M0-48c-9.8 0-17.7 7.8-17.7 17.4 0 15.5 17.7 30.6 17.7 30.6s17.7-15.4 17.7-30.6c0-9.6-7.9-17.4-17.7-17.4z',
            fillColor: '#ff7600',
            fillOpacity: 1,
            strokeWeight: 0,
            scale: 0.5,
          }}
          onClick={() => {
            onClickMarker(pickupPoint.id);
          }}
        />
      ))}
    </Map>
  );
}

Environment

  • Library version: 0.4.1
  • Google maps version: weekly
  • Browser and Version: Chrome 119.0.6045.199
  • OS: MacOS

Logs

No response

Interleaved support with DeckGL

I would like to open a discussion about Interleaved support.

Imagine that you have a DeckGL project where you want to change between MapLibre and Google Maps with interleave control. For MapLibre you would have something like this

<DeckGL
    initialViewState={INITIAL_VIEW_STATE}
    layers={layers}
    controller={true}
    onViewStateChange={...}>
    <MapLibre />
  </DeckGL>

where the camera control is done by Deck but if you change it to Google Maps with interleave support you would have something like this example where the map camera control is done by the Google Maps component instead DeckGL.

It's difficult to maintain and it can also be difficult to integrate with libraries like NebulaGL that interacts directly with the map events. So my question is if there would be a way to support interleave while keeping Deck as a wrapper of the map, for example:

<DeckGL
    initialViewState={INITIAL_VIEW_STATE}
    layers={layers}
    controller={true}
    onViewStateChange={...}>
    <Map interleaved={true} {...GOOGLE_MAPS_MAP_OPTIONS} />
  </DeckGL>

ControlPosition not positioning where it should

Hello!

I was testing the library with this simple component:

"use client";

import { APIProvider, ControlPosition, Map } from "@vis.gl/react-google-maps";

export default function GoogleMap() {
  const position = { lat: 53.54, lng: 10 };

  return (
    <APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? ""}>
      <div className="aspect-video max-w-lg">
        <Map
          zoom={9}
          center={position}
          disableDefaultUI
          zoomControl
          zoomControlOptions={{
            position: ControlPosition.LEFT_TOP, // <----
          }}
        />
      </div>
    </APIProvider>
  );
}

but the zoom control is not being position at the LEFT_TOP:

image

If I use the same constant from google.maps.ControlPosition it works as expected. Although they both seem to point to 17, at runtime google.maps.ControlPosition seems to evaluate to 5:

"use client";

import { APIProvider, ControlPosition, Map } from "@vis.gl/react-google-maps";
import { useEffect, useState } from "react";

export default function GoogleMap() {
  const position = { lat: 53.54, lng: 10 };
  const [zoomControlPosition, setZoomControlPosition] = useState(
    ControlPosition.LEFT_TOP
  );

  useEffect(() => {
    setTimeout(() => {
      console.log("ControlPosition.LEFT_TOP:", ControlPosition.LEFT_TOP);
      console.log(
        "google.maps.ControlPosition.LEFT_TOP:",
        google.maps.ControlPosition.LEFT_TOP // <---- this logs 5 instead of 17
      );
      setZoomControlPosition(google.maps.ControlPosition.LEFT_TOP);
    }, 2000);
  }, []);

  return (
    <APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY ?? ""}>
      <div className="aspect-video max-w-lg">
        <Map
          zoom={9}
          center={position}
          disableDefaultUI
          zoomControl
          zoomControlOptions={{
            position: zoomControlPosition,
          }}
        />
      </div>
    </APIProvider>
  );
}
image

What is the plan for stable version?

I'm excited to see this library
Is there a plan with clear milestones and ETA for first stable version?
So we can help to focus on these milestones

onClick on AdvancedMarker to change Map zoom changes only once [Bug]

Description

In ReactJS the <Map zoom={zoom} .. > set via useState changes only once, next consecutive clicks on the AdvancedMarker don't affect the Map zoom (despite the react state changing correctly).

Steps to Reproduce

create a react state like [zoom, setZoom] = useState(15)
assign the state to the Map component as <Map zoom={zoom} .. ></Map>
set zoom level in a onClick inside as:

...
[zoom, setZoom] = useState(15)

const markerClick = () => {
  setZoom(18);
}
...
<APIProvider apiKey={mapsApi} libraries={['marker']}>
  <Map mapId={myMapID} zoom={zoom}>
    <AdvancedMarker onClick={() => markerClick()}>
       ...
    </AdvancedMarker>
  </Map>
</APIProvider>

then zoom out either with gestures or the zoom controls, and try to click the AdvancedMarker again, it won't zoom at 18 anymore.

Environment

  • Library version: ^0.4.2
  • Google maps version: weekly
  • Browser and Version: Google Chrome 120.0.6099.217 (Official Build) (64-bit)
  • OS: Windows 11

Logs

No response

Can't import the named export 'Children' from non EcmaScript module (only default export is available)

Description

Hi everyone, I installed the package in my current project and tried to replicate the basic example. At build-time I get the following error:

./node_modules/@vis.gl/react-google-maps/dist/index.modern.mjs
Can't import the named export 'Children' from non EcmaScript module (only default export is available)

I could be a problem coming from my environment but I wanted to know if anyone has ever encountered something similar.
Thanks in advance.

Steps to Reproduce

  • install vis.gl/react-google-maps
  • create a simple component
const GoogleMapsTest: React.FC = () => {

  return (
    <APIProvider apiKey={API_KEY}>
      <Map
        zoom={3}
        center={{ lat: 22.54992, lng: 0 }}
        gestureHandling={'greedy'}
        disableDefaultUI={true}
      />
    </APIProvider>
  )
}
  • import it in a route

Environment

  • Library version: "@vis.gl/react-google-maps": "^0.4.1"
  • Google maps version: weekly
  • Browser and Version: Chrome 120
  • OS: Mac OSX Ventura - Apple M2 Pro

Dependencies versions (from package.json):

"@mapbox/mapbox-gl-draw": "1.3.0",
"react": "17.0.2",
"react-scripts": "4.0.3",

Logs

No response

[Feat] ImageMapType Support

Target Use Case

Add the ability to add tile layers. For example, a component that could be used to consume the new Air Quality heat map tile endpoint. Currently, I am not sure what the recommended best practice for tile layers would be with this library, perhaps using the DeckGL overlay and adding via DeckGL overlay's layers prop?

Proposal

I'm thinking the </Map /> component could accept <TileLayer /> components as children. TileLayer component would accept props similar to its current API: getTileUrl, opacity, getTileSize, etc.

import {APIProvider, Map, TileLayer } from '@vis.gl/react-google-maps';

function App() {
  const position = {lat: 53.54992, lng: 10.00678};

  return (
    <APIProvider apiKey={'YOUR API KEY HERE'}>
      <Map center={position} zoom={10}>
        <TileLayer getTileUrl={(coord, zoom) => `https://airquality.googleapis.com/v1/mapTypes/US_AQI/heatmapTiles/${zoom}/${coord.x}/${coord.y}key=${API_KEY}` 
    }  />
      </Map>
    </APIProvider>
  );
}

export default App;

MapId change is not working

The first time that you try to change the Map ID you get this warning

Google Maps JavaScript API: A Map's mapId property cannot be changed after initial Map render 

but the change happens. The second time that you try it, it doesn't work.

I think that the problem comes from this line that prevents a new instance of the map from being created

Map is not draggable using NextJS [Bug]

Description

I'm trying to set up the simple example in a NextJS environment, but the map isn't draggable.
Cloning the simple example with vite works, but I cannot get it to work with Next.

Steps to Reproduce

Code sandbox: https://codesandbox.io/p/devbox/f5mzxf

This is minimal next setup, showing the map. The map is not draggable even though the same configuration in vite leads to a draggable map.

Environment

  • Library version: 0.6.0
  • Google maps version: weekly
  • Browser and Version: Chrome 121.0.6167.139
  • OS: Mac Sonoma 14.3

Logs

No response

[Feat] Custom Info Window component

Target Use Case

The current infoWindow class doesn't allow for granular control over styling or even the close button. Having an infoWindow that can render a React.ReactNode (or even a static HTML element but the dev would have full control of it as opposed to what you get from infoWindow) would allow for making maps that look and feel much cleaner with the rest of the web app.

Proposal

I have implemented a Custom Info Window by extending the google.maps.Overlay class but I was only able to make it render static HTML elements, not React components. Maybe the use of React portals to render React components would work? I haven't tried it yet myself.

Or maybe this is the completely wrong approach and there's a better way of doing it.

[Bug] setting center via onCenterChanged on Map causes the map to move without the markers

Description

Screen.Recording.2023-11-29.at.11.42.14.AM.mov

Steps to Reproduce

Create a state:
const [center, setCenter] = useState<google.maps.LatLngLiteral>(MAP_CENTER);

Create a map and change the center with onCenterChanged

<Map
            center={center}
            zoom={zoom}
            minZoom={6}
            mapTypeControl={false}
            onZoomChanged={(e) => {
              console.log(e.detail.zoom);
              setZoom(e.detail.zoom);
            }}
            onCenterChanged={(e) => {
              console.log(e.detail.center);
              setCenter(e.detail.center)
            }}          >
          ** markers code removed for brevity **
</Map>

NOTE: I have tried with both Marker and AdvancedMarker

Environment

  • Library version:
  • Google Maps version: weekly
  • "@vis.gl/react-google-maps": "^0.3.3"
  • Browser and Version: Arc - 1.17.2
  • OS: macOS Sonoma 14.0

Logs

No errors

mapTypeId change is not working

This issue is very similar to this one but in this case the library doesn't detect a mapTypeId change. I mean, if you keep the same mapId but you change the mapTypeId the map is not reloaded

ReferenceError: React is not defined on Marker and InfoWindow components

Hello! I'm trying to get a minimal example up and running in Next.js and I'm unable to get the Marker or InfoWindow components rendering (AdvancedMarker works fine).

I believe the issue is due to their use of a fragment <></>. The compiled version of this (in dist/index.modern.mjs imports React as React$1, and most of the references are to this, but for some reason on a fragment, it is referencing React without the $1.

import React$1, { useState, useMemo, useEffect, useCallback, useContext, useLayoutEffect, forwardRef, useImperativeHandle, Children, useRef } from 'react';

const Marker = forwardRef((props, ref) => {
  const marker = useMarker(props);
  useImperativeHandle(ref, () => marker, [marker]);
  return /*#__PURE__*/React.createElement(Fragment, null);
});
function useMarkerRef() {
  const [marker, setMarker] = useState(null);
  const refCallback = useCallback(m => {
    setMarker(m);
  }, []);
  return [refCallback, marker];
}

The error is being displayed as:

Screenshot 2023-09-24 at 3 34 56 PM

The example app where I am running into the error can be found at https://github.com/leighhalliday/next-react-google-maps-bug

If I modify the source to refer to <Fragment></Fragment> (vs <></>), the generated dist file works correctly. This makes me think it has something to do with microbundle:

{
  "build:microbundle": "microbundle -o dist/index.js -f modern,umd --globals react=React,react-dom=ReactDOM --jsx React.createElement --no-compress --tsconfig tsconfig.build.json",
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.