In this React tutorial, we’re going to embed a Google Map with advanced feature components like Draggable Marker, Fetch Address of current point using Geocoder and Place search, or Autocomplete in React application by using a widely used and popular third-party package name google-map-react
.
Google Maps javascript API provides a number of methods and features to create awesome and super interactive Geolocation based maps. In the React application, where we have a composition of sections for each control, we cannot simply use this API.
To implement Google Maps in a React application we are going to use a third party packages module named google-map-react
.
The google-map-react package can be used to create custom components for embedding an interactive and fully-features Google map on a page.
Today we are going to create Google maps having the following features:
- Get the current position using navigator API to set the center of Map.
- Add Autocomplete/ Search bar for places to locate the map center for selected places.
- Draggable Marker in Map, to show coordinates of the current position.
- Fetch address using Geocoder service on load and change of marker position.
- For touch devices, a user can tab on the Map instead of drag to change position.
[lwptoc]
Create a React Application
First, we’ll create a new React application using npx create-react-app
command
$ npx create-react-app react-google-maps-app
Move inside the react app
$ cd react-google-maps-app
Run application
$ npm start
Install Required Package
For using Google Maps API component in React application, install the google-map-react
package.
$ npm install --save google-map-react
Also, we’ll install the styled-components
package, this is used to add in-component styling to the components.
$ npm install --save styled-components
Create New Components
Before we start, create three new class components for Marker, Autocomplete, and GoogleMap. In the components folder create the following files:
- ‘~components/Marker.js’
- ‘~components/Autocomplete.js’
- ‘~components/MyGoogleMap.js’
We’ll use Marker and Autocomplete components inside the MyGoogleMap components.
Update Marker Functional Component
The Marker component will handle the click events, having local style using styled-components.
Update the ‘~components/Marker.js‘ file with the following code:
// Marker.js
import React from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
const Wrapper = styled.div`
position: absolute;
width: 38px;
height: 37px;
background-image: url(https://icon-library.com/images/pin-icon-png/pin-icon-png-9.jpg);
background-size: contain;
background-repeat: no-repeat;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-transform: translate(-50%,-50%);
-ms-transform: translate(-50%,-50%);
transform: translate(-50%,-50%);
cursor: grab;
`;
const Marker = ({ text, onClick }) => (
<Wrapper
alt={text}
onClick={onClick}
/>
);
Marker.defaultProps = {
onClick: null,
};
Marker.propTypes = {
onClick: PropTypes.func,
text: PropTypes.string.isRequired,
};
export default Marker;
Update Autocomplete Class Component
Now, we’ll update the Autocomplete class component. This will have an event listener for place changed using the places
API.
This will render an <input />
control event to clear query and also return the selected place from Autosuggestion to parent component.
Update the ‘~components/Autocomplete.js’ file with the following code:
// Autocomplete.js
import React, { Component } from 'react';
import styled from 'styled-components';
const Wrapper = styled.div`
position: relative;
align-items: center;
justify-content: center;
width: 100%;
padding: 20px;
text-align:center;
`;
class AutoComplete extends Component {
constructor(props) {
super(props);
this.clearSearchBox = this.clearSearchBox.bind(this);
}
componentDidMount({ map, mapApi } = this.props) {
const options = {
// restrict your search to a specific type of result
types: ['address'],
// restrict your search to a specific country, or an array of countries
// componentRestrictions: { country: ['gb', 'us'] },
};
this.autoComplete = new mapApi.places.Autocomplete(
this.searchInput,
options,
);
this.autoComplete.addListener('place_changed', this.onPlaceChanged);
this.autoComplete.bindTo('bounds', map);
}
componentWillUnmount({ mapApi } = this.props) {
mapApi.event.clearInstanceListeners(this.searchInput);
}
onPlaceChanged = ({ map, addplace } = this.props) => {
const place = this.autoComplete.getPlace();
if (!place.geometry) return;
if (place.geometry.viewport) {
map.fitBounds(place.geometry.viewport);
} else {
map.setCenter(place.geometry.location);
map.setZoom(17);
}
addplace(place);
this.searchInput.blur();
};
clearSearchBox() {
this.searchInput.value = '';
}
render() {
return (
<Wrapper>
<input
className="search-input"
ref={(ref) => {
this.searchInput = ref;
}}
type="text"
onFocus={this.clearSearchBox}
placeholder="Enter a location"
/>
</Wrapper>
);
}
}
export default AutoComplete;
Update MyGoogleMap Component
Finally, we’ll update the MyGoogleMap
component to embed the Google Map.
# Initialize Class State
In this component class, we’ll have the state
to control Google map APIs and coordinates.
state = {
mapApiLoaded: false,
mapInstance: null,
mapApi: null,
geoCoder: null,
places: [],
center: [],
zoom: 9,
address: '',
draggable: true,
lat: null,
lng: null
};
# Get Browser Geolocation
By using the browser’s navigator object we’ll fetch the current position of the user.
setCurrentLocation() {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
this.setState({
center: [position.coords.latitude, position.coords.longitude],
lat: position.coords.latitude,
lng: position.coords.longitude
});
});
}
}
And call this method inside the componentWillMount()
hook.
componentWillMount() {
this.setCurrentLocation();
}
# Render Google Map
Inside the render method of the class, we’ll now add the <Autocomplete />
component and pass google API instances to it using props.
<AutoComplete map={mapInstance} mapApi={mapApi} addplace={this.addPlace} />
The Google map will be rendered by adding the <GoogleMapReact />
component. This will be enriched with many properties and event handlers.
<GoogleMapReact
center={this.state.center}
zoom={this.state.zoom}
draggable={this.state.draggable}
onChange={this._onChange}
onChildMouseDown={this.onMarkerInteraction}
onChildMouseUp={this.onMarkerInteractionMouseUp}
onChildMouseMove={this.onMarkerInteraction}
onChildClick={() => console.log('child click')}
onClick={this._onClick}
bootstrapURLKeys={{
key: 'YOUR_API_KEY',
libraries: ['places', 'geometry'],
}}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({ map, maps }) => this.apiHasLoaded(map, maps)}
>
<Marker
text={this.state.address}
lat={this.state.lat}
lng={this.state.lng}
/>
</GoogleMapReact>
let’s have a look at properties used:
bootstrapURLKeys
: This object will have the Google API key and the libraries you need.
yesIWantToUseGoogleMapApiInternals
: For adding a custom Map initialize method we need to add this property.
onGoogleApiLoaded
: This event handler is triggered when Google Map API is loaded.
There are also some event handlers on Map and its internal components like Marker.
# Add Marker Component
Inside the <GoogleMapReact/>
component, add the <Marker/>
component with location coordinates. These coordinates will be updated on load, drag, and place search change when the internal state is updated.
# Show Address using Geocode Service API
We are also singing the Geocode service to fetch address by passing the coordinates. The addresses array is getting converted into a readable string with the help of the _generateAddress()
function.
_generateAddress() {
const {
mapApi
} = this.state;
const geocoder = new mapApi.Geocoder;
geocoder.geocode({ 'location': { lat: this.state.lat, lng: this.state.lng } }, (results, status) => {
console.log(results);
console.log(status);
if (status === 'OK') {
if (results[0]) {
this.zoom = 12;
this.setState({ address: results[0].formatted_address });
} else {
window.alert('No results found');
}
} else {
window.alert('Geocoder failed due to: ' + status);
}
});
}
# Finally Update the MyGoogleMap Component
The MyGoogleMap
component will have the following code:
// MyGoogleMaps.js
import React, { Component } from 'react';
import GoogleMapReact from 'google-map-react';
import styled from 'styled-components';
import AutoComplete from './Autocomplete';
import Marker from './Marker';
const Wrapper = styled.main`
width: 100%;
height: 100%;
`;
class MyGoogleMap extends Component {
state = {
mapApiLoaded: false,
mapInstance: null,
mapApi: null,
geoCoder: null,
places: [],
center: [],
zoom: 9,
address: '',
draggable: true,
lat: null,
lng: null
};
componentWillMount() {
this.setCurrentLocation();
}
onMarkerInteraction = (childKey, childProps, mouse) => {
this.setState({
draggable: false,
lat: mouse.lat,
lng: mouse.lng
});
}
onMarkerInteractionMouseUp = (childKey, childProps, mouse) => {
this.setState({ draggable: true });
this._generateAddress();
}
_onChange = ({ center, zoom }) => {
this.setState({
center: center,
zoom: zoom,
});
}
_onClick = (value) => {
this.setState({
lat: value.lat,
lng: value.lng
});
}
apiHasLoaded = (map, maps) => {
this.setState({
mapApiLoaded: true,
mapInstance: map,
mapApi: maps,
});
this._generateAddress();
};
addPlace = (place) => {
this.setState({
places: [place],
lat: place.geometry.location.lat(),
lng: place.geometry.location.lng()
});
this._generateAddress()
};
_generateAddress() {
const {
mapApi
} = this.state;
const geocoder = new mapApi.Geocoder;
geocoder.geocode({ 'location': { lat: this.state.lat, lng: this.state.lng } }, (results, status) => {
console.log(results);
console.log(status);
if (status === 'OK') {
if (results[0]) {
this.zoom = 12;
this.setState({ address: results[0].formatted_address });
} else {
window.alert('No results found');
}
} else {
window.alert('Geocoder failed due to: ' + status);
}
});
}
// Get Current Location Coordinates
setCurrentLocation() {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
this.setState({
center: [position.coords.latitude, position.coords.longitude],
lat: position.coords.latitude,
lng: position.coords.longitude
});
});
}
}
render() {
const {
places, mapApiLoaded, mapInstance, mapApi,
} = this.state;
return (
<Wrapper>
{mapApiLoaded && (
<div>
<AutoComplete map={mapInstance} mapApi={mapApi} addplace={this.addPlace} />
</div>
)}
<GoogleMapReact
center={this.state.center}
zoom={this.state.zoom}
draggable={this.state.draggable}
onChange={this._onChange}
onChildMouseDown={this.onMarkerInteraction}
onChildMouseUp={this.onMarkerInteractionMouseUp}
onChildMouseMove={this.onMarkerInteraction}
onChildClick={() => console.log('child click')}
onClick={this._onClick}
bootstrapURLKeys={{
key: 'AIzaSyAM9uE4Sy2nWFfP-Ha6H8ZC6ghAMKJEKps',
libraries: ['places', 'geometry'],
}}
yesIWantToUseGoogleMapApiInternals
onGoogleApiLoaded={({ map, maps }) => this.apiHasLoaded(map, maps)}
>
<Marker
text={this.state.address}
lat={this.state.lat}
lng={this.state.lng}
/>
</GoogleMapReact>
<div className="info-wrapper">
<div className="map-details">Latitude: <span>{this.state.lat}</span>, Longitude: <span>{this.state.lng}</span></div>
<div className="map-details">Zoom: <span>{this.state.zoom}</span></div>
<div className="map-details">Address: <span>{this.state.address}</span></div>
</div>
</Wrapper >
);
}
}
export default MyGoogleMap;
# Add CSS Style
In the App.css file add following CSS style for map components.
.main-wrapper {
height: 60vh;
margin: 10px 50px;
filter: drop-shadow(-1px 5px 3px #ccc);
}
.info-wrapper {
margin-top: 15px;
}
.map-details {
text-align: center;
font-size: 1.2em;
}
.map-details span {
font-weight: bold;
}
.search-input {
font-size: 1.2em;
width: 80%;
}
Adding Google Map in App
After creating the components, we need to show it in the React application’s App page. To render the Google map inside the application, update the App.js file with the following code:
// App.js
import React from 'react';
import './App.css';
import MyGoogleMap from './components/MyGoogleMap';
function App() {
return (
<div className="main-wrapper">
<MyGoogleMap />
</div>
);
}
export default App;
That’s it, now run the application by hitting $ npm start
to open the app in the browser.
Conclusion
Finally, we’are done with the implementation of Google Maps in React application. Here we discussed how to add draggable Marker in Google map with Autocomplete place search. The draggable marker doesn’t work on touch devices so we used the map click event to update the Marker’s position when a user taps anywhere on the map.
Below the map, we are showing current marker position coordinates, Zoom level, and address which is converted to readable form using a helper function.
Hope this was helpful, do share your comments and feedback.
Leave a Reply