Thursday, 11 October, 2018 UTC


Summary

This tutorial aims at integrating the google maps API to your React components and enabling you to display maps on your website.
Introduction to Google Maps API.
At some point of our lives, we've all gotten a chance to use and interact with the google maps, either through finding directions, viewing our current location, ordering cabs or estimating distance and time from one point to another.
Including the Google maps API in a React App is actually far much easier than expected due to its rich documentation which I highly suggest that you look into, and the npm package by Fullstack React.
Prerequisite:
  • React Knowledge (at least intermediate level)
  • A Google Maps API Key
Go on ahead and grab your API key here.
  1. Simply click on the Get Started button
  2. Tick on the maps checkbox
  3. Click Create A New Project
  4. Once you’ve named your project (whatever you want) you can set up your billing info which will automatically enable your the API and generate the API key.
PS: Do not be hesitant of adding your billing info as the Google cloud platform offers you a 12-month free trial period and will not bill you after the trial period until you give your permission.
Project Setup
For some quick setup, we are going to use facebook's create-react-app which saves us the hassle of having to configure webpack or babel.
So go on ahead and run this command
npm i -g create-react-app
create-react-app my-googlemap
cd my-googlemap
Before we add any code, let’s go ahead and install our dependency.
npm install --save google-maps-react
Let's go on and edit our src folder and remove files and imports that we do not need ie
  1. logo.svg
  2. App.css
  3. index.css
  4. In index.js remove the css import.

Time to create our component.

We will need to edit our App.js file and instead have our component that will load our google map.
import React, { Component } from 'react';
import { Map, GoogleApiWrapper } from 'google-maps-react';

const mapStyles = {
  width: '100%',
  height: '100%'
};

export class MapContainer extends Component {
  render() {
    return (
      <Map
        google={this.props.google}
        zoom={14}
        style={mapStyles}
        initialCenter={{
         lat: -1.2884,
         lng: 36.8233
        }}
      />
    );
  }
}

export default GoogleApiWrapper({
  apiKey: 'YOUR_GOOGLE_API_KEY_GOES_HERE'
})(MapContainer);
For a simple Google Map, this is literally all the code you need. Go ahead and run your app and ensure that the map loads to the browser.
The Map component takes in some optional props such as style(the CSS style object), Zoom(number value representing a tighter focus on the map's center) and initialCenter(an object containing latitude and longitude coordinates)
The GoogleApiWrapper is simply a Higher Order Component(HOC) that provides wrapper around Google APIs. Alternatively, the GoogleApiWrapper HOC can be configured by passing a function that will be called with the wrapped component's props and should return the configuration object like so.
export default GoogleApiWrapper(
  (props) => ({
    apiKey: props.apiKey
  }
))(MapContainer)
Markers and infoWindow
Wouldn't it be exciting to have a Marker and an infoWindow on our map showing our set initialCenter position? So let's add this functionality to our code.
First we need to import Marker and infoWindow components from the google-maps-react library inorder to help us achieve loading of the two.
import { GoogleApiWrapper, InfoWindow, Marker } from 'google-maps-react';
Notice that our component before was stateless?, We need to add state for state management.
[...]

export class MapContainer extends Component {
   state = {
    showingInfoWindow: false,  //Hides or the shows the infoWindow
    activeMarker: {},          //Shows the active marker upon click
    selectedPlace: {}          //Shows the infoWindow to the selected place upon a marker
  };
Next, we need to add event handlers for when the map and the marker are clicked.
  onMarkerClick = (props, marker, e) =>
    this.setState({
      selectedPlace: props,
      activeMarker: marker,
      showingInfoWindow: true
    });

  onClose = props => {
    if (this.state.showingInfoWindow) {
      this.setState({
        showingInfoWindow: false,
        activeMarker: null
      });
    }
  };
  • The onMarkerClick is used to show the InfoWindow which is a component in the google-maps-react library which gives us the ability for a pop-up window showing details of the clicked place/marker.
Sidenote: the visibility of the infoWindow is controlled by the boolean visible prop which shows the InfoWindow component when true and hides it when false.
  • The onClose basically is for closing the InfoWindow once a user clicks on the close button on the infoWindow.
Let's complete our component by adding our Marker and InfoWindow components to our render method
  render() {
    return (
      <Map
        google={this.props.google}
        zoom={14}
        style={style}
        initialCenter={{ lat: -1.2884, lng: 36.8233 }
      >
        <Marker
          onClick={this.onMarkerClick}
          name={'Kenyatta International Convention Centre'}
        />
        <InfoWindow
          marker={this.state.activeMarker}
          visible={this.state.showingInfoWindow}
          onClose={this.onClose}
        >
          <div>
            <h4>{this.state.selectedPlace.name}</h4>
          </div>
        </InfoWindow>
      </Map>
    );
  }
}

[...]
Run your app and ensure you have the one marker with the infoWindow upon click.
  • TODO: Try and add a few more markers on your map and more interactivity to your infoWindow.
Browser's current location
Let's spice things up by having our map pick our browser's current location. We will be using navigator which is a read-only property that returns a Geolocation object that gives Web content access to the location of the device.
In our src folder create a new file and name it Map.js and create a component named CurrentLocation this is where all our functionality to pick our browser's location will lie.
We will begin by adding some default props to our CurrentLocation component, since we will need to set the map with a center incase the current location is not provided.This is handled by the boolean prop centerAroundCurrentLocation
import React from 'react';
import ReactDOM from 'react-dom';

const mapStyles = {
  map: {
    position: 'absolute',
    width: '100%',
    height: '100%'
  }
};

export class CurrentLocation extends React.Component {

[...]

}
export default CurrentLocation;

CurrentLocation.defaultProps = {
  zoom: 14,
  initialCenter: {
    lat: -1.2884,
    lng: 36.8233
  },
  centerAroundCurrentLocation: false,
  visible: true
};
Next, we need to make our component stateful,
[...]

export class CurrentLocation extends React.Component {
  constructor(props) {
    super(props);

    const { lat, lng } = this.props.initialCenter;
    this.state = {
      currentLocation: {
        lat: lat,
        lng: lng
      }
    };
  }

[...]

}
Let's also update our CurrentLocation component to cater for the instance when the Map is first loaded as we cannot solely depend upon the google API being always available, hence we need to check if it's loaded. And also check if the browser's current location is provided and recenter the map to it.
[...]

componentDidUpdate(prevProps, prevState) {
    if (prevProps.google !== this.props.google) {
      this.loadMap();
    }
    if (prevState.currentLocation !== this.state.currentLocation) {
      this.recenterMap();
    }
  }

[...]
Let's define the recenterMap() function which only gets called when the currentLocation in the component's state is updated and uses the .panTo() method on the google.maps.Map instance to change the center of the map.
[...]

 recenterMap() {
    const map = this.map;
    const current = this.state.currentLocation;

    const google = this.props.google;
    const maps = google.maps;

    if (map) {
      let center = new maps.LatLng(current.lat, current.lng);
      map.panTo(center);
    }
  }

[...]
Next, we need to handle the instance when the map has already loaded.This will be handled by the componentDidMount() Lifecycle method which will set a call back to fetch the current location.
[...]

 componentDidMount() {
    if (this.props.centerAroundCurrentLocation) {
      if (navigator && navigator.geolocation) {
        navigator.geolocation.getCurrentPosition(pos => {
          const coords = pos.coords;
          this.setState({
            currentLocation: {
              lat: coords.latitude,
              lng: coords.longitude
            }
          });
        });
      }
    }
    this.loadMap();
  }

[...]
Notice the loadMap() function? Let's go on ahead and define it.
[...]

loadMap() {
    if (this.props && this.props.google) {
      // checks if google is available
      const { google } = this.props;
      const maps = google.maps;

      const mapRef = this.refs.map;

      // reference to the actual DOM element
      const node = ReactDOM.findDOMNode(mapRef);

      let { zoom } = this.props;
      const { lat, lng } = this.state.currentLocation;
      const center = new maps.LatLng(lat, lng);
      const mapConfig = Object.assign(
        {},
        {
          center: center,
          zoom: zoom
        }
      );

      // maps.Map() is constructor that instantiates the map
      this.map = new maps.Map(node, mapConfig);
    }
  }
Basically, the loadMap() function is only called after the component has been rendered and grabs a reference to the DOM component to where we want our map to be placed.
Our CurrentLocation component is almost looking up, But we need to ensure that our previous Marker picks our currenct location ie the browsers current location and so we need to introduce Parent-Child concept through the renderChildren() method which will be responsible for actually calling the method on the child component.
Read more about Parent-Child communication here
[...]

 renderChildren() {
    const { children } = this.props;

    if (!children) return;

    return React.Children.map(children, c => {
      if (!c) return;
      return React.cloneElement(c, {
        map: this.map,
        google: this.props.google,
        mapCenter: this.state.currentLocation
      });
    });
  }

[...]
And finally, let's add our render() method
[...]

render() {
     const style = Object.assign({}, mapStyles.map);
    return (
      <div>
        <div style={style} ref="map">
          Loading map...
        </div>
        {this.renderChildren()}
      </div>
    );
  }

[...]
Lastly, before we wind up we need to update our MapContainer component to include our new changes. So let's change it to this
import React, { Component } from 'react';
import { GoogleApiWrapper, InfoWindow, Marker } from 'google-maps-react';

import CurrentLocation from './Map';

export class MapContainer extends Component {
  state = {
    showingInfoWindow: false,
    activeMarker: {},
    selectedPlace: {}
  };

  onMarkerClick = (props, marker, e) =>
    this.setState({
      selectedPlace: props,
      activeMarker: marker,
      showingInfoWindow: true
    });

  onClose = props => {
    if (this.state.showingInfoWindow) {
      this.setState({
        showingInfoWindow: false,
        activeMarker: null
      });
    }
  };

  render() {
    return (
      <CurrentLocation
        centerAroundCurrentLocation
        google={this.props.google}
      >
        <Marker onClick={this.onMarkerClick} name={'current location'} />
        <InfoWindow
          marker={this.state.activeMarker}
          visible={this.state.showingInfoWindow}
          onClose={this.onClose}
        >
          <div>
            <h4>{this.state.selectedPlace.name}</h4>
          </div>
        </InfoWindow>
      </CurrentLocation>
    );
  }
}

export default GoogleApiWrapper({
  apiKey: 'YOUR_GOOGLE_API_KEY_GOES_HERE'
})(MapContainer);
Run your app; heading over to our browser, our map should first load with our initialCenter then reload to pick our browser's current location with the marker positioned to this location and Voilà we are done.
Conclusion
In this article we've been able to load our google maps React component, add a marker and have an infoWindow onto it and also have the map pick our current location. While we did not dive into adding more functionalities to our map such as, having polylines and polygons or adding event listeners, I highly recommend that you look into them