Google Maps in React: Autocomplete Location Search | Draggable Marker | Marker Infobox

I will teach you how to integrate Google Maps in your React Project.
You will also learn how to create an autocomplete location search and draggable marker. When you search the location and select one the marker will automatically move to that location and display the address in the infobox.

We will use the following npm modules to achieve this:
1-react
2-react-google-maps 
3-react-google-autocomplete
4-react-geocode

Get the API Key

Get your API Key from developers.google.com
You can also watch tutorial to Get API Key

YouTube Tutorial:

Completed Code: https://github.com/imranhsayed/google-maps-in

Step 1Install Boilerplate: You can either use your own project or You can clone the boilerplate from below link for testing.

git clone https://github.com/imranhsayed/react-node-boilerplate
cd react-node-boilerplate
npm cache clean --force // in root dir
npm install
cd client // in client dir
npm cache clean --force
npm install

Step 2: Install react-google-maps, react-google-autocomplete and react-geocode npm modules.

cd client
npm i react-google-maps react-google-autocomplete react-geocode
cd ..
npm run dev

Step 3: Create Map.js Component

Create a Map.js component inside client/src/components directory and put your API key here that you created above

import React from 'react'
import { withGoogleMap, GoogleMap, withScriptjs, InfoWindow, Marker } from "react-google-maps";
import Geocode from "react-geocode";
Geocode.setApiKey("xxxxx");
Geocode.enableDebug();
class Map extends React.Component{
constructor( props ){
super( props );
this.state = {
address: '',
city: '',
area: '',
state: '',
mapPosition: {
lat: this.props.center.lat,
lng: this.props.center.lng
},
markerPosition: {
lat: this.props.center.lat,
lng: this.props.center.lng
}
}
}
/**
* Get the current address from the default map position and set those values in the state
*/
componentDidMount() {
Geocode.fromLatLng( this.state.mapPosition.lat , this.state.mapPosition.lng ).then(
response => {
const address = response.results[0].formatted_address,
addressArray = response.results[0].address_components,
city = this.getCity( addressArray ),
area = this.getArea( addressArray ),
state = this.getState( addressArray );

console.log( 'city', city, area, state );

this.setState( {
address: ( address ) ? address : '',
area: ( area ) ? area : '',
city: ( city ) ? city : '',
state: ( state ) ? state : '',
} )
},
error => {
console.error(error);
}
);
};
/**
* Component should only update ( meaning re-render ), when the user selects the address, or drags the pin
*
* @param nextProps
* @param nextState
* @return {boolean}
*/
shouldComponentUpdate( nextProps, nextState ){
if (
this.state.markerPosition.lat !== this.props.center.lat ||
this.state.address !== nextState.address ||
this.state.city !== nextState.city ||
this.state.area !== nextState.area ||
this.state.state !== nextState.state
) {
return true
} else if ( this.props.center.lat === nextProps.center.lat ){
return false
}
}
/**
* Get the city and set the city input value to the one selected
*
* @param addressArray
* @return {string}
*/
getCity = ( addressArray ) => {
let city = '';
for( let i = 0; i < addressArray.length; i++ ) {
if ( addressArray[ i ].types[0] && 'administrative_area_level_2' === addressArray[ i ].types[0] ) {
city = addressArray[ i ].long_name;
return city;
}
}
};
/**
* Get the area and set the area input value to the one selected
*
* @param addressArray
* @return {string}
*/
getArea = ( addressArray ) => {
let area = '';
for( let i = 0; i < addressArray.length; i++ ) {
if ( addressArray[ i ].types[0] ) {
for ( let j = 0; j < addressArray[ i ].types.length; j++ ) {
if ( 'sublocality_level_1' === addressArray[ i ].types[j] || 'locality' === addressArray[ i ].types[j] ) {
area = addressArray[ i ].long_name;
return area;
}
}
}
}
};
/**
* Get the address and set the address input value to the one selected
*
* @param addressArray
* @return {string}
*/
getState = ( addressArray ) => {
let state = '';
for( let i = 0; i < addressArray.length; i++ ) {
for( let i = 0; i < addressArray.length; i++ ) {
if ( addressArray[ i ].types[0] && 'administrative_area_level_1' === addressArray[ i ].types[0] ) {
state = addressArray[ i ].long_name;
return state;
}
}
}
};
/**
* And function for city,state and address input
* @param event
*/
onChange = ( event ) => {
this.setState({ [event.target.name]: event.target.value });
};
/**
* This Event triggers when the marker window is closed
*
* @param event
*/
onInfoWindowClose = ( event ) => {
};
render(){
const AsyncMap = withScriptjs(
withGoogleMap(
props => (
<GoogleMap google={this.props.google}
defaultZoom={this.props.zoom}
defaultCenter={{ lat: this.state.mapPosition.lat, lng: this.state.mapPosition.lng }}
>
</GoogleMap>
)
)
);
let map;
  if( this.props.center.lat !== undefined ) {
   map = <div>
     <div>
      <div className="form-group">
       <label htmlFor="">City</label>
       <input type="text" name="city" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.city }/>
      </div>
      <div className="form-group">
       <label htmlFor="">Area</label>
       <input type="text" name="area" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.area }/>
      </div>
      <div className="form-group">
       <label htmlFor="">State</label>
       <input type="text" name="state" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.state }/>
      </div>
      <div className="form-group">
       <label htmlFor="">Address</label>
       <input type="text" name="address" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.address }/>
      </div>
     </div>
     <AsyncMap
      googleMapURL="https://maps.googleapis.com/maps/api/js?key=xxxxx&libraries=places"
      loadingElement={
       <div style={{ height: `100%` }} />
      }
      containerElement={
       <div style={{ height: this.props.height }} />
      }
      mapElement={
       <div style={{ height: `100%` }} />
      }
     />
    </div>
} else {
map = <div style={{height: this.props.height}} />
}
return( map )
}
}
export default Map

Marker Component

import { Marker } from "react-google-maps";
{/*Marker*/}
<Marker google={this.props.google}
name={'Dolores park'}
draggable={true}
onDragEnd={ this.onMarkerDragEnd }
position={{ lat: this.state.markerPosition.lat, lng: this.state.markerPosition.lng }}
/>
<Marker />

InfoWindow Component

import { InfoWindow } from "react-google-maps";
component Map extends Component {
/**
* This Event triggers when the marker window is closed
*
* @param event
*/
onInfoWindowClose = ( event ) => {
};
render(){
return(
{/* InfoWindow on top of marker */}
<InfoWindow
onClose={this.onInfoWindowClose}
position={{ lat: ( this.state.markerPosition.lat + 0.0018 ), lng: this.state.markerPosition.lng }}
>
<div>
<span style={{ padding: 0, margin: 0 }}>{ this.state.address }</span>
</div>
</InfoWindow>
)
}

Autocomplete or Search location

import Autocomplete from 'react-google-autocomplete';
component Map extends Component {
/**
* When the user types an address in the search box
* @param place
*/
onPlaceSelected = ( place ) => {
const address = place.formatted_address,
addressArray = place.address_components,
city = this.getCity( addressArray ),
area = this.getArea( addressArray ),
state = this.getState( addressArray ),
latValue = place.geometry.location.lat(),
lngValue = place.geometry.location.lng();
// Set these values in the state.
this.setState({
address: ( address ) ? address : '',
area: ( area ) ? area : '',
city: ( city ) ? city : '',
state: ( state ) ? state : '',
markerPosition: {
lat: latValue,
lng: lngValue
},
mapPosition: {
lat: latValue,
lng: lngValue
},
})
};
render(){
return(
{/* For Auto complete Search Box */}
<Autocomplete
style={{
width: '100%',
height: '40px',
paddingLeft: '16px',
marginTop: '2px',
marginBottom: '100px'
}}
onPlaceSelected={ this.onPlaceSelected }
types={['(regions)']}
/>
)
}

Putting Everything together

import React from 'react'
import { withGoogleMap, GoogleMap, withScriptjs, InfoWindow, Marker } from "react-google-maps";
import Autocomplete from 'react-google-autocomplete';
import Geocode from "react-geocode";
Geocode.setApiKey("xxxx");
Geocode.enableDebug();
class Map extends React.Component{
constructor( props ){
super( props );
this.state = {
address: '',
city: '',
area: '',
state: '',
mapPosition: {
lat: this.props.center.lat,
lng: this.props.center.lng
},
markerPosition: {
lat: this.props.center.lat,
lng: this.props.center.lng
}
}
}
/**
* Get the current address from the default map position and set those values in the state
*/
componentDidMount() {
Geocode.fromLatLng( this.state.mapPosition.lat , this.state.mapPosition.lng ).then(
response => {
const address = response.results[0].formatted_address,
addressArray = response.results[0].address_components,
city = this.getCity( addressArray ),
area = this.getArea( addressArray ),
state = this.getState( addressArray );

console.log( 'city', city, area, state );

this.setState( {
address: ( address ) ? address : '',
area: ( area ) ? area : '',
city: ( city ) ? city : '',
state: ( state ) ? state : '',
} )
},
error => {
console.error(error);
}
);
};
/**
* Component should only update ( meaning re-render ), when the user selects the address, or drags the pin
*
* @param nextProps
* @param nextState
* @return {boolean}
*/
shouldComponentUpdate( nextProps, nextState ){
if (
this.state.markerPosition.lat !== this.props.center.lat ||
this.state.address !== nextState.address ||
this.state.city !== nextState.city ||
this.state.area !== nextState.area ||
this.state.state !== nextState.state
) {
return true
} else if ( this.props.center.lat === nextProps.center.lat ){
return false
}
}
/**
* Get the city and set the city input value to the one selected
*
* @param addressArray
* @return {string}
*/
getCity = ( addressArray ) => {
let city = '';
for( let i = 0; i < addressArray.length; i++ ) {
if ( addressArray[ i ].types[0] && 'administrative_area_level_2' === addressArray[ i ].types[0] ) {
city = addressArray[ i ].long_name;
return city;
}
}
};
/**
* Get the area and set the area input value to the one selected
*
* @param addressArray
* @return {string}
*/
getArea = ( addressArray ) => {
let area = '';
for( let i = 0; i < addressArray.length; i++ ) {
if ( addressArray[ i ].types[0] ) {
for ( let j = 0; j < addressArray[ i ].types.length; j++ ) {
if ( 'sublocality_level_1' === addressArray[ i ].types[j] || 'locality' === addressArray[ i ].types[j] ) {
area = addressArray[ i ].long_name;
return area;
}
}
}
}
};
/**
* Get the address and set the address input value to the one selected
*
* @param addressArray
* @return {string}
*/
getState = ( addressArray ) => {
let state = '';
for( let i = 0; i < addressArray.length; i++ ) {
for( let i = 0; i < addressArray.length; i++ ) {
if ( addressArray[ i ].types[0] && 'administrative_area_level_1' === addressArray[ i ].types[0] ) {
state = addressArray[ i ].long_name;
return state;
}
}
}
};
/**
* And function for city,state and address input
* @param event
*/
onChange = ( event ) => {
this.setState({ [event.target.name]: event.target.value });
};
/**
* This Event triggers when the marker window is closed
*
* @param event
*/
onInfoWindowClose = ( event ) => {
};
/**
* When the user types an address in the search box
* @param place
*/
onPlaceSelected = ( place ) => {
const address = place.formatted_address,
addressArray = place.address_components,
city = this.getCity( addressArray ),
area = this.getArea( addressArray ),
state = this.getState( addressArray ),
latValue = place.geometry.location.lat(),
lngValue = place.geometry.location.lng();
// Set these values in the state.
this.setState({
address: ( address ) ? address : '',
area: ( area ) ? area : '',
city: ( city ) ? city : '',
state: ( state ) ? state : '',
markerPosition: {
lat: latValue,
lng: lngValue
},
mapPosition: {
lat: latValue,
lng: lngValue
},
})
};
/**
* When the marker is dragged you get the lat and long using the functions available from event object.
* Use geocode to get the address, city, area and state from the lat and lng positions.
* And then set those values in the state.
*
* @param event
*/
onMarkerDragEnd = ( event ) => {
console.log( 'event', event );
let newLat = event.latLng.lat(),
newLng = event.latLng.lng(),
addressArray = [];
Geocode.fromLatLng( newLat , newLng ).then(
response => {
const address = response.results[0].formatted_address,
addressArray = response.results[0].address_components,
city = this.getCity( addressArray ),
area = this.getArea( addressArray ),
state = this.getState( addressArray );
this.setState( {
address: ( address ) ? address : '',
area: ( area ) ? area : '',
city: ( city ) ? city : '',
state: ( state ) ? state : ''
} )
},
error => {
console.error(error);
}
);
};
render(){
const AsyncMap = withScriptjs(
withGoogleMap(
props => (
<GoogleMap google={this.props.google}
defaultZoom={this.props.zoom}
defaultCenter={{ lat: this.state.mapPosition.lat, lng: this.state.mapPosition.lng }}
>
{/* For Auto complete Search Box */}
<Autocomplete
style={{
width: '100%',
height: '40px',
paddingLeft: '16px',
marginTop: '2px',
marginBottom: '100px'
}}
onPlaceSelected={ this.onPlaceSelected }
types={['(regions)']}
/>
{/*Marker*/}
<Marker google={this.props.google}
name={'Dolores park'}
draggable={true}
onDragEnd={ this.onMarkerDragEnd }
position={{ lat: this.state.markerPosition.lat, lng: this.state.markerPosition.lng }}
/>
<Marker />
{/* InfoWindow on top of marker */}
<InfoWindow
onClose={this.onInfoWindowClose}
position={{ lat: ( this.state.markerPosition.lat + 0.0018 ), lng: this.state.markerPosition.lng }}
>
<div>
<span style={{ padding: 0, margin: 0 }}>{ this.state.address }</span>
</div>
</InfoWindow>
</GoogleMap>
)
)
);
let map;
  if( this.props.center.lat !== undefined ) {
   map = <div>
     <div>
      <div className="form-group">
       <label htmlFor="">City</label>
       <input type="text" name="city" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.city }/>
      </div>
      <div className="form-group">
       <label htmlFor="">Area</label>
       <input type="text" name="area" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.area }/>
      </div>
      <div className="form-group">
       <label htmlFor="">State</label>
       <input type="text" name="state" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.state }/>
      </div>
      <div className="form-group">
       <label htmlFor="">Address</label>
       <input type="text" name="address" className="form-control" onChange={ this.onChange } readOnly="readOnly" value={ this.state.address }/>
      </div>
     </div>
     <AsyncMap
      googleMapURL="https://maps.googleapis.com/maps/api/js?key=xxxxx&libraries=places"
      loadingElement={
       <div style={{ height: `100%` }} />
      }
      containerElement={
       <div style={{ height: this.props.height }} />
      }
      mapElement={
       <div style={{ height: `100%` }} />
      }
     />
    </div>
} else {
map = <div style={{height: this.props.height}} />
}
return( map )
}
}
export default Map

Step 4: Import Map into another component

// Now export the above Map component into another component by passing the props values, lets say src/Home
class Home extends Component {
  render() {
    return(
        <Map
     google={this.props.google}
     center={{lat: 18.5204, lng: 73.8567}}
     height='300px'
     zoom={15}
    />
      )
  }
}

Leave a Reply