How to Fit Boundaries and Center React Google Maps

Viewed 1620

I am new to React Google Maps. I am using this library to create a map with several locations and trying fit the boundaries and center it. I have used this example.

After I populate the map, I want the map to change its center based on the places which have been populated rather than the default center. I am unable to achieve this. The following is my code.

import React, { useState, useEffect } from "react"
import {
  GoogleMap,
  LoadScript,
  Marker,
  InfoWindow,
} from "@react-google-maps/api"

const Map = () => {
  const mapStyles = {
    height: "400px",
    width: "100%",
  }

  const defaultCenter = {
    lat: 39.76866069032195,
    lng: -86.15818042985295,
  }

  const locations = [
    {
      name: "Location 1",
      location: {
        lat: 41.3954,
        lng: 2.162,
      },
    },
    {
      name: "Location 2",
      location: {
        lat: 41.3917,
        lng: 2.1649,
      },
    },
    {
      name: "Location 3",
      location: {
        lat: 41.3773,
        lng: 2.1585,
      },
    },
    {
      name: "Location 4",
      location: {
        lat: 41.3797,
        lng: 2.1682,
      },
    },
    {
      name: "Location 5",
      location: {
        lat: 41.4055,
        lng: 2.1915,
      },
    },
  ]

  const [selected, setSelected] = useState({})
  const [map, setMap] = useState(null)

  const onSelect = item => {
    setSelected(item)
  }

  const Somefunction = map => {
    const bounds = new window.google.maps.LatLngBounds()
    locations.map((location, i) => {
      bounds.extend(new window.google.maps.LatLng(location.lat, location.lng))
    })
    map.fitBounds(bounds)
    setMap(map)
  }

  return (
    <div className="map-container">
      <LoadScript googleMapsApiKey="AIzaSyBTNL1jQzyjzbgp_yRmFPlPPQ6UOQAeyI0">
        <GoogleMap
          mapContainerStyle={mapStyles}
          zoom={13}
          center={defaultCenter}
          onLoad={map => {
            const bounds = new window.google.maps.LatLngBounds()
            map.fitBounds(bounds)
            setMap(map)
          }}
        >
          {locations.map(item => {
            return (
              <Marker
                key={item.name}
                position={item.location}
                onClick={() => onSelect(item)}
              />
            )
          })}
          {selected.location && (
            <InfoWindow
              position={selected.location}
              clickable={true}
              onCloseClick={() => setSelected({})}
            >
              <p>{selected.name}</p>
            </InfoWindow>
          )}
        </GoogleMap>
      </LoadScript>
    </div>
  )
}
export default Map

With the current code, the map gets centered in some bizarre location and not in the middle of the 5 locations listed above.

I would also appreciate if someone can shed light on how to change the zoom level for different screen size automatically.

1 Answers

The problem I see in the use of functional components is that it is not as flexible as using class based components in terms of this kind of implementation. To be more precise, it would be more beneficial to make use of the react life cycles such as ComponentDidMount which you can only do when using class based components. Furthermore, you may want to stay away from using 3rd party packages/libraries and instead implement it locally by dynamically adding the script. That way, you would be able to follow Google's official documentation instead of the the 3rd party's. The following is a sample I made to demonstrate this as well as making use of the bounds:

StackBlitz sample: https://stackblitz.com/edit/react-bounds-65524739

or

App.js

import React, { Component } from "react";
import { render } from "react-dom";
import Map from "./components/map";
import "./style.css";

class App extends Component {
  render() {
    return (
      <Map
        id="myMap"
        options={{
          center: { lat: 39.76866069032195, lng: -86.15818042985295 },
          zoom: 8
        }}
      />
    );
  }
}

export default App;

map.js

import React, { Component } from "react";
import { render } from "react-dom";

class Map extends Component {
  constructor(props) {
    super(props);
    this.state = {
      map: "",
      locations: [
        {
          name: "Location 1",
          location: {
            lat: 41.3954,
            lng: 2.162
          }
        },
        {
          name: "Location 2",
          location: {
            lat: 41.3917,
            lng: 2.1649
          }
        },
        {
          name: "Location 3",
          location: {
            lat: 41.3773,
            lng: 2.1585
          }
        },
        {
          name: "Location 4",
          location: {
            lat: 41.3797,
            lng: 2.1682
          }
        },
        {
          name: "Location 5",
          location: {
            lat: 41.4055,
            lng: 2.1915
          }
        }
      ]
    };
  }

  onScriptLoad() {
    const map = new window.google.maps.Map(
      document.getElementById(this.props.id),
      this.props.options
    );
    this.addMarker(map);
  }

  addMarker(map) {
    const bounds = new window.google.maps.LatLngBounds();
    this.state.locations.map((location, i) => {
      new google.maps.Marker({
        position: location.location,
        map: map
      });

      bounds.extend(
        new window.google.maps.LatLng(
          location.location.lat,
          location.location.lng
        )
      );
    });
    map.fitBounds(bounds);
  }

  componentDidMount() {
    if (!window.google) {
      var s = document.createElement("script");
      s.type = "text/javascript";
      s.src = `https://maps.google.com/maps/api/js?key=YOUR_API_KEY`;
      var x = document.getElementsByTagName("script")[0];
      x.parentNode.insertBefore(s, x);
      // Below is important.
      //We cannot access google.maps until it's finished loading
      s.addEventListener("load", e => {
        this.onScriptLoad();
      });
    } else {
      this.onScriptLoad();
    }
  }

  render() {
    return <div className="map" id={this.props.id} />;
  }
}

export default Map;

Related