javascriptreactjsgoogle-mapsgoogle-maps-api-3react-google-maps

@react-google-maps make map open and center POI


I am using @react-google-maps/api with React 17.0.2 for a small project. I currently have a list of coordinates, and when I click various buttons, the map centers itself on a marker at one of those coordinates. However, a lot of the places I want to center on are "POI"s, and they already have built in markers and an info box with the correct name, address, and a link to google maps. (Seen HERE) Is it possible to make the map center on and open a specified POI? I have not found any documentation or examples of this. Simplified current code below:

const pinLocations = {
  location1: { lat: 1, lng: 1 },
  location2: { lat: 2, lng: 2 }
};

const Example = () => {
  const { isLoaded } = useJsApiLoader({
    googleMapsApiKey: API_KEY,
  });

  const [center, setCenter] = useState(pinLocations.location1);

  function updateMap(pinLocation) {
    setCenter(pinLocation);
  }

  return (
    <GoogleMap zoom={11} position={center}>
      <Marker position={center} />
    </GoogleMap>
      
    <Button onClick={() => updateMap(pinLocations.location1)}>
      Location 1
    </Button>
    <Button onClick={() => updateMap(pinLocations.location2)}>
      Location 2
    </Button>
  );  
}

Solution

  • It's possible. I was able to somehow replicate what the tactile map had through reverse geocoding with @react-google-maps/api library.

    I did try to make it look like your sample above, and here's what I did:

    I made a sample array of markers:

        const markers = [
          {
            id: 1,
            position: { lat: 14.5432, lng: 121.0473 }
          },
          {
            id: 2,
            position: { lat: 14.5468, lng: 121.0543 }
          }
        ];
    

    Inside the Map() component, I made two hooks: One for the active marker, and also one for the infoWindow address result.

    const [activeMarker, setActiveMarker] = useState(null);
    const [address, setAddress] = useState("Address");
    

    Then this is how I structured the <GoogleMap> component:

    return (
      <GoogleMap
        onLoad={handleOnLoad}
        onClick={() => setActiveMarker(null)}
        mapContainerStyle={{ width: "100vw", height: "100vh" }}
      >
      {/*maps the array of markers above*/}
      {markers.map(({ id, position }) => (
        <Marker
          key={id}
          position={position}
          onClick={() => handleActiveMarker(position, id)}
        >
          {/*show the infoWindow of the Active marker*/}
          {activeMarker === id ? (
            <InfoWindow
              onCloseClick={() => setActiveMarker(null)}
              options={{ maxWidth: 200 }}
            >
              <div>{address}</div>
            </InfoWindow>
          ) : null}
        </Marker>
      ))}
      </GoogleMap>
    );
    

    The functions inside the <GoogleMap> are handleOnLoad()(for loading the map with bounds), and handleActiveMarker(for showing infoWindow and reverse geocoding the address inside the infoWindow).

    #1

    //loads the map with bounds
    const handleOnLoad = (map) => {
      const bounds = new google.maps.LatLngBounds();
      markers.forEach(({ position }) => bounds.extend(position));
      map.fitBounds(bounds);
    };
    

    #2

    //function for marker onClick
    const handleActiveMarker = (position, id) => {
      if (id === activeMarker) {
        return;
      }
      console.log(id);
      /*this sets the current active marker 
      to help in what infoWindow to open*/
      setActiveMarker(id);
      // console.log(position);
    
      //start a geocoder here>>
      const geocoder = new google.maps.Geocoder();
      // console.log(geocoder);
      geocoder
        .geocode({ location: position })
        .then((response) => {
          if (response.results[0]) {
            let fullAddress = response.results[0].formatted_address;
            //this changes the address default state on infoWindow
            setAddress(fullAddress);
          } else {
            window.alert("No results found");
          }
        })
        .catch((e) => window.alert("Geocoder failed due to: " + e));
        return;
    };
    

    I did not include a setCenter programmatically because I noticed the tactile map that you were referring to did not have one. It already automatically fits the infoWindow on the viewport.

    Here's a codesandbox link if you need a reproducible example: https://codesandbox.io/s/react-google-maps-api-multiple-markers-infowindow-forked-0zjogb?file=/src/Map.js

    note: Use your own API Key.

    Hope this helps.