Search code examples
javascriptreactjsgoogle-mapsgoogle-maps-api-3react-google-maps

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


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!


Solution

  • 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;