How to access google.maps.Map object with react-google-maps

18,652

Solution 1

After thoroughly reading through the react-google-maps documentation, examples, and issues I have come to learn that the package does not support a lot of the things I will need to do for my application.

That being said, I have begun writing my own Google Maps API wrapper based off of the work done by Fullstack React. I've omitted a lot of the utilities used in the below mentioned as they can be found here or here.

That being said my solution is to wrap the google maps container in a higher order component and expose the Map Object via the window object:

App

const App = ({ store }) => (
  <Provider store={store}>
    <div>
      <Sidebar>
        <StartingPoint />
        {/* TODO */}
      </Sidebar>
      <GoogleMap />
    </div>
  </Provider>
);

containers/GoogleMap/wrapper.jsx Google Map Higher Order Component wraps GoogleMap Container

const defaultCreateCache = (options) => {
  const opts = options || {};
  const apiKey = opts.apiKey;
  const libraries = opts.libraries || ['places'];
  const version = opts.version || '3.24';
  const language = opts.language || 'en';

  return ScriptCache({
    google: GoogleApi({
      apiKey,
      language,
      libraries,
      version,
    }),
  });
};

const wrapper = options => (WrappedComponent) => {
  const createCache = options.createCache || defaultCreateCache;

  class Wrapper extends Component {
    constructor(props, context) {
      super(props, context);

      this.scriptCache = createCache(options);
      this.scriptCache.google.onLoad(this.onLoad.bind(this));

      this.state = {
        loaded: false,
        google: null,
      };
    }

    onLoad() {
      this.GAPI = window.google;

      this.setState({ loaded: true, google: this.GAPI });
    }

    render() {
      const props = Object.assign({}, this.props, {
        loaded: this.state.loaded,
        google: window.google,
      });
      const mapRef = (el) => { this.map = el; };

      return (
        <div>
          <WrappedComponent {...props} />
          <div ref={mapRef} />
        </div>
      );
    }
  }
  Wrapper.propTypes = {
    dispatchGoogleAPI: PropTypes.func,
  };
  Wrapper.defaultProps = {
    dispatchGoogleAPI: null,
  };

  return Wrapper;
};

export default wrapper;

containers/GoogleMap/index.jsx Google Map Container

class Container extends Component {
  constructor(props) {
    super(props);

    this.loadMap = this.loadMap.bind(this);
    this.calcRoute = this.calcRoute.bind(this);
  }

  componentDidUpdate() {
    const { origin, destination, route } = this.props;

    this.calcRoute(origin, destination);
  }

  loadMap(node) {
    if (this.props && this.props.google) {
      const { google } = this.props;

      // instantiate Direction Service
      this.directionsService = new google.maps.DirectionsService();

      this.directionsDisplay = new google.maps.DirectionsRenderer({
        suppressMarkers: true,
      });

      const zoom = 13;
      const mapTypeId = google.maps.MapTypeId.ROADMAP;
      const lat = 37.776443;
      const lng = -122.451978;
      const center = new google.maps.LatLng(lat, lng);

      const mapConfig = Object.assign({}, {
        center,
        zoom,
        mapTypeId,
      });

      this.map = new google.maps.Map(node, mapConfig);

      this.directionsDisplay.setMap(this.map);

      // make the map instance available to other components
      window.map = this.map
    }
  }

  calcRoute(origin, destination) {
    const { google, route } = this.props;

    if (!origin && !destination && !route) return;

    const waypts = [];

    waypts.push({
      location: new google.maps.LatLng(37.415284, -122.076899),
      stopover: true,
    });

    const start = new google.maps.LatLng(origin.lat, origin.lng);
    const end = new google.maps.LatLng(destination.lat, destination.lng);

    this.createMarker(end);

    const request = {
      origin: start,
      destination: end,
      waypoints: waypts,
      optimizeWaypoints: true,
      travelMode: google.maps.DirectionsTravelMode.DRIVING,
    };

    this.directionsService.route(request, (response, status) => {
      if (status === google.maps.DirectionsStatus.OK) {
        this.directionsDisplay.setDirections(response);
        const route = response.routes[0];
        console.log(route);
      }
    });

    this.props.calculateRoute(false);
  }

  createMarker(latlng) {
    const { google } = this.props;

    const marker = new google.maps.Marker({
      position: latlng,
      map: this.map,
    });
  }

  render() {
    return (
      <div>
        <GoogleMapView loaded={this.props.loaded} loadMap={this.loadMap} />
      </div>
    );
  }
}

const GoogleMapContainer = wrapper({
  apiKey: ('YOUR_API_KEY'),
  version: '3', // 3.*
  libraries: ['places'],
})(Container);

const mapStateToProps = state => ({
  origin: state.Trip.origin,
  destination: state.Trip.destination,
  route: state.Trip.route,
});

const mapDispatchToProps = dispatch => ({
  dispatchGoogleMap: (map) => {
    dispatch(googleMap(map));
  },
  calculateRoute: (route) => {
    dispatch(tripCalculation(route));
  },
});

const GoogleMap = connect(mapStateToProps, mapDispatchToProps)(GoogleMapContainer);

export default GoogleMap;

Solution 2

You can do it by React refs:

<GoogleMap ref={(map) => this._map = map} />
function someFunc () { 
    //using, for example as:
    this._map.getCenter() 
    this._map.setZoom(your desired zoom);
}

Solution 3

What I did right now in my react-redux application is I assign global variable map outside of react comnponent GoogleMap:

/*global google*/

// your imports //

var map;

class GoogleMap extends Component {
  constructor(props) {
    super(props);

    this.state = {
      // your states
    };
  }

  // your functions

  componentWillReceiveProps(nextProps) {

  }

  componentDidMount() {

    // code

    // render googlemap

    map = new google.maps.Map(this.refs.map, yourMapProps);

    // add click event listener to the map

    map.addListener('click', function(e) {
      //code
    });

    //viewport listener

    map.addListener('idle', function(){
      // code
    });
  }

  render() {
      return (
        <div id="map" ref="map">
          {places.map((place) => {
             return(<Marker place={place} key={place.key} map={map} />);
          })}
        </div>
  }
}

function mapDispatchToProps(dispatch) {
   //code
}

export default connect(mapDispatchToProps)(GoogleMap);

Pass map as props into Child Component:

/*global google*/

import React, { Component } from 'react';

class Marker extends Component {
  componentDidMount() {
    this.renderMarker();
  }

  renderMarker() {
    var { place, map } = this.props;
    place.setMap(map);
  }

  render() {
    return null;
  }
}

export default Marker;

I don't know is it good practice. Bu it works. I tried to find the solution how to avoid setting Map Object as global windows.map reading all this stuff about singletons and so on. And then this came to my head. Now if I type window.map in the browser concole I get div id="map"

Solution 4

Worth pointing out for anyone else googling this that nowdays, using react-google-maps you can simply use the useGoogleMap hook to get access to the Google maps instance

https://react-google-maps-api-docs.netlify.app/#map-instance

import React from 'react'
import { useGoogleMap } from '@react-google-maps/api'

function PanningComponent() {
  const map = useGoogleMap()

  React.useEffect(() => {
    if (map) {
      map.panTo(...)
    }
  }, [map])

  return null
}

Solution 5

import {GoogleMap, withGoogleMap} from 'react-google-maps';
import {MAP} from 'react-google-maps/lib/constants';

const MapComponent = withGoogleMap(() => (
 {/*Here you have access to google.maps.Map object*/}
     <GoogleMap ref={(map) => map.context[MAP]}/>
 ));


const Map = ({locations}) => (
  <MapComponentClass
    containerElement={MapComponent}
    mapElement={MapComponent}
    locations={locations}/>
);

export default Map;
Share:
18,652
j_quelly
Author by

j_quelly

I’m a Canadian frontend (user experience) engineer with fullstack proficiency working in San Francisco. I specialize in creating delightful, performant, user interfaces and experiences. My excellent product thinking and observations ensure there is no task I cannot handle.

Updated on June 08, 2022

Comments

  • j_quelly
    j_quelly almost 2 years

    I have a pretty simple react application using https://github.com/tomchentw/react-google-maps but I'm having difficulty understanding how to get a reference to my current map or how to access the google.maps.Map object in a custom component.

    I found this on the repo, but after reading through the posts I'm still a little confused.

    I'm starting my application building off of the DirectionsRenderer example.

    What I want to do next is add my own custom components for picking the starting point and using the Google Maps autocomplete API.

    Yes, I know that the package has a component for that already, but I need to do a little more than just search for a location on the map.

    In order to accomplish my needs I will do something like

    const autocomplete = new google.maps.places.Autocomplete(node);
    autocomplete.bindTo('bounds', map);
    

    Where node is the element I'm binding the autocomplete functionality and map is an instance of the google.maps.Map object.

    My application thus far:

    App.jsx

    const App = ({ store }) => (
      <Provider store={store}>
        <div>
          <Sidebar>
            <StartingPoint defaultText="Choose starting point&hellip;" />
          </Sidebar>
          <GoogleApiWrapper />
        </div>
      </Provider>
    );
    

    GoogleApiWrapper

    const GoogleMapHOC = compose(
      withProps({
        googleMapURL: 'https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places&key=__GAPI_KEY',
        loadingElement: <div style={{ height: '100vw' }} />,
        containerElement: <div style={{ height: '100vh' }} />,
        mapElement: <div style={{ height: '100%' }} />,
      }),
      withScriptjs,
      withGoogleMap,
      lifecycle({
        componentDidMount() {
          const DirectionsService = new google.maps.DirectionsService();
    
          // make google object available to other components
          this.props.onLoad(google);
    
          DirectionsService.route({
            origin: new google.maps.LatLng(41.8507300, -87.6512600),
            destination: new google.maps.LatLng(41.8525800, -87.6514100),
            travelMode: google.maps.TravelMode.DRIVING,
          }, (result, status) => {
            if (status === google.maps.DirectionsStatus.OK) {
              this.setState({
                directions: result,
              });
            } else {
              console.error(`error fetching directions ${result}`);
            }
          });
        },
      }),
    )(props => (
      <GoogleMap
        ref={props.onMapMounted}
        defaultZoom={13}
        defaultCenter={new google.maps.LatLng(37.771336, -122.446615)}
      >
        {props.directions && <DirectionsRenderer directions={props.directions} />}
      </GoogleMap>
    ));
    

    If I'm unable to access the google.maps.Map object outside of the wrapper I would alternatively like to access a reference to the element that contains the map so that I may instantiate a new google.maps.Map(ref_to_elem, options);

    Any help would be greatly appreciated!

  • Alessandro Sassi
    Alessandro Sassi almost 3 years
    to use this hook it has to be in a deeper level than the GoogleMap component