de__bug de__bug - 29 days ago 6
Javascript Question

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 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 and 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!

Answer Source

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 dispatch the map instance via redux:

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;

    console.log(origin, destination, route);

    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 as it is just data
      this.props.dispatchGoogleMap(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;