ReactJS: Google Map Component

ReactJS: Google Map Component

Steps to implement google maps in Facebook’s ReactJS Web Application

1. Add npm module in your project for react-google-maps

npm install --save react-google-maps # or
yarn add react-google-maps

 

Reference : https://github.com/tomchentw/react-google-maps

Before this you have to include google maps api sdk in your index.ejs file 

<script src=”https://maps.googleapis.com/maps/api/js?v=3.27&libraries=places,geometry&key=<YOUR_API_KEY>”></script>

How to get Google Maps Api Key ? 

Follow the steps given in this link

2. Create Google Map Handler Component

'use strict';

import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import update from 'react-addons-update';

import canUseDOM from 'can-use-dom';

import raf from 'raf';
import GoogleMapComponent from './gMapComponent';

const geolocation = (
  canUseDOM && navigator.geolocation ?
  navigator.geolocation :
  ({
    getCurrentPosition(success, failure) {
      failure('Your browser doesnt support geolocation.');
    },
  })
);

class GoogleMapHandlerComponent extends React.Component {

    state = {
      bounds: null,
      center: null,
      content: null,
      radius: 400,
      markers: []
    };

  constructor(props) {
    super(props);
    if (props !== null &&
        props !== undefined &&
        props.location !== null &&
        props.location !== undefined &&
        props.location.markers !== null &&
        props.location.markers !== undefined) {
      const locationInfo = {
        center: props.location.center,
        markers: props.location.markers
      };
      this.state = locationInfo;
    }
  }

  componentDidMount() {
    const tick = () => {
      if (this.isUnmounted) {
        return;
      }
      this.setState({ radius: Math.max(this.state.radius - 5, 0) });
      if (this.state.radius > 10) {
        raf(tick);
      }
    };
    geolocation.getCurrentPosition((position) => {
      if (this.isUnmounted) {
        return;
      }
      this.setState({
        center: {
          lat: position.coords.latitude,
          lng: position.coords.longitude,
        },
        content: 'Location found using HTML5.',
      });

      raf(tick);
    }, (reason) => {
      if (this.isUnmounted) {
        return;
      }
      this.setState({
        center: {
          lat: 60,
          lng: 105,
        },
        content: 'Error: The Geolocation service failed (${reason}).',
      });
    });

    if (document.getElementsByClassName('pac-container').length > 0) {
      const gmapSearchResultsBox = document.getElementsByClassName('pac-container')[0];
      gmapSearchResultsBox.style.zIndex = '2000';
    }
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps !== null &&
        nextProps !== undefined &&
        nextProps.location !== null &&
        nextProps.location !== undefined &&
        nextProps.location.markers !== null &&
        nextProps.location.markers !== undefined) {
      this.setState({
        center: nextProps.location.center,
        markers: nextProps.location.markers
      });
    }
  }

  componentDidUpdate() {
    if (document.getElementsByClassName('pac-container').length > 0) {
      const gmapSearchResultsBox = document.getElementsByClassName('pac-container')[0];
      gmapSearchResultsBox.style.zIndex = '2000';
    }
  }
  componentWillUnmount() {
    this.isUnmounted = true;
  }

  isUnmounted = false;

 handleMapMounted = this.handleMapMounted.bind(this);
 handleBoundsChanged = this.handleBoundsChanged.bind(this);
 handleSearchBoxMounted = this.handleSearchBoxMounted.bind(this);
 handlePlacesChanged = this.handlePlacesChanged.bind(this);
 handleMarkerClose = this.handleMarkerClose.bind(this);
 handleMarkerClick = this.handleMarkerClick.bind(this);

 handleMapMounted(map) {
     this._map = map;
   }

   handleBoundsChanged() {
     this.setState({
       bounds: this._map.getBounds(),
       center: this._map.getCenter(),
     });
   }

   handleSearchBoxMounted(searchBox) {
     this._searchBox = searchBox;
   }
   handleLoadPlaces(markers) {

   }
   handlePlacesChanged() {
     const places = this._searchBox.getPlaces();

     // Add a marker for each place returned from search bar
     const markers = places.map(place => ({
       position: place.geometry.location,
     }));

     // Set markers; set map center to first search result
     const mapCenter = markers.length > 0 ? markers[0].position : this.state.center;
     this.setState({
       center: mapCenter,
       markers,
     });
     this.props.handleLocationChange(mapCenter, places);
   }

   // Toggle to 'true' to show InfoWindow and re-renders component
  handleMarkerClick(targetMarker) {
    this.setState({
      markers: this.state.markers.map(marker => {
        if (marker === targetMarker) {
          if (marker.showInfo) {
            return {
              ...marker,
              showInfo: false,
            };
          }
          return {
            ...marker,
            showInfo: true,
          };
        }
        return marker;
      }),
    });
  }

  handleMarkerClose(targetMarker) {
    this.setState({
      markers: this.state.markers.map(marker => {
        if (marker === targetMarker) {
          return {
            ...marker,
            showInfo: false,
          };
        }
        return marker;
      }),
    });
  }

  render() {
    return (
      <GoogleMapComponent
        containerElement={
          <div style={{ height: '100%' }} />
        }
        mapElement={
          <div style={{ height: '100%' }} />
        }
        center={this.state.center}
        onMapMounted={this.handleMapMounted}
        onBoundsChanged={this.handleBoundsChanged}
        onSearchBoxMounted={this.handleSearchBoxMounted}
        bounds={this.state.bounds}
        onPlacesChanged={this.handlePlacesChanged}
        onMarkerClick={this.handleMarkerClick}
        onMarkerClose={this.handleMarkerClose}
        markers={this.state.markers}
      />
    );
  }
}

 function mapDispatchToProps(dispatch) {
   return bindActionCreators({ }, dispatch);
 }

export default connect(null, mapDispatchToProps)(GoogleMapHandlerComponent);

 

3. Create Google Map Component, this component uses the components of react-google-maps module

'use strict';

import React from 'react';
import IconButton from 'material-ui/IconButton';

import {
  withGoogleMap,
  GoogleMap,
  Circle,
  InfoWindow,
  Marker
} from 'react-google-maps';

import SearchBox from 'react-google-maps/lib/places/SearchBox';
import GoogleMapMarkerInfoWindow from './gMapMarkerInfoWindow';

import layoutStyle from '../../../constants/layoutStyle';

const INPUT_STYLE = {
  boxSizing: 'border-box',
  MozBoxSizing: 'border-box',
  border: '1px solid transparent',
  width: '240px',
  height: '32px',
  marginTop: '27px',
  marginRight: '20px',
  padding: '0 12px',
  borderRadius: '1px',
  boxShadow: '0 2px 6px rgba(0, 0, 0, 0.3)',
  fontSize: '14px',
  outline: 'none',
  textOverflow: 'ellipses',
};

const GoogleMapComponent = withGoogleMap(props => (
  <GoogleMap
    ref={props.onMapMounted}
    defaultZoom={15}
    center={props.center}
    onBoundsChanged={props.onBoundsChanged}
  >
    <SearchBox
      ref={props.onSearchBoxMounted}
      bounds={props.bounds}
      controlPosition={google.maps.ControlPosition.TOP_RIGHT}
      onPlacesChanged={props.onPlacesChanged}
      inputPlaceholder='Search Location'
      inputStyle={INPUT_STYLE}
    />

    {props.markers.map((marker, index) => (
      <Marker
        key={index}
        position={marker.position}
        onClick={() => props.onMarkerClick(marker)}
      >
        {/*
           Show info window only if the 'showInfo' key of the marker is true.
           That is, when the Marker pin has been clicked and 'onCloseClick' has been
           Successfully fired.
        */}
        {marker.showInfo && (marker.pinOnly === null ||
        marker.pinOnly === undefined || !marker.pinOnly) && (
          <InfoWindow onCloseClick={() => props.onMarkerClose(marker)}>
            <GoogleMapMarkerInfoWindow infoContent={marker.infoContent} />
          </InfoWindow>
        )}
      </Marker>
    ))}

    {props.center && (
      <Circle
        center={props.center}
        radius={props.radius}
        options={{
          fillColor: 'red',
          fillOpacity: 0.20,
          strokeColor: 'red',
          strokeOpacity: 1,
          strokeWeight: 1,
        }}
      />
    )}
  </GoogleMap>
));

export default GoogleMapComponent;


4. Use Google Map Handler component whereever you wish to place the map

render() {
      const handleLocationChange = this.handleLocationChange;
      const mapsComponent = (
        <GoogleMapHandlerComponent
          location={this.props.location}
          handleLocationChange={handleLocationChange.bind(this)}
        />);
      return (
        <div
          id='mapId'
          style={{ width: '450px', height: '350px' }}
        >
          {mapsComponent}
        </div>
      );
    }

Note you have to pass your current location to land user on his current location, else any other location which is default location.
and a callback for handling change in location.

ReactJS google map location change callback will be called by react-google-maps library when there is change in location,
like user searched some location

Also check stateless react components : http://knowledge-cess.com/reactjs-functional-or-stateless-components/