If you ever wondered how to do geocoding with React.js, then this tutorial is for you.
Geocoding is the process of converting addresses (like "1600 Amphitheatre Parkway, Mountain View, CA") into geographic coordinates.
In this tutorial we're going to build React.js component that implements this application:

You can find the full source code in this GitHub repository.
We'll start by creating new React.js component called Application:
var React = require('react');
var Application = React.createClass({
render: function () {
return ({/* JSX code */});
}
});
module.exports = Application;
Our component doesn't render anything, yet.
What should it render? From Figure 1 you can clearly see the following 3 user interface elements:
Let's start with the search. We'll write JSX code that creates <form> element:
<div className="container">
<div className="row">
<div className="col-sm-12">
<form className="form-inline">
<div className="row">
<div className="col-xs-8 col-sm-10">
<div className="form-group">
<label className="sr-only" htmlFor="address">Address</label>
<input type="text"
className="form-control input-lg"
id="address"
placeholder="London"
required />
</div>
</div>
<div className="col-xs-4 col-sm-2">
<button type="submit" className="btn btn-default btn-lg">
<span className="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
Whoa, that's alot of <div> elements! Do we really need them?
Not really. Their purpose is to create a layout using Bootstrap grid system. If you're not familiar with Bootstrap - don't worry, just focus on <form> element:
<div className="container">
{/* ... */}
<form className="form-inline">
{/* ... */}
<label className="sr-only" htmlFor="address">Address</label>
<input type="text"
className="form-control input-lg"
id="address"
placeholder="London"
required />
{/* ... */}
<button type="submit" className="btn btn-default btn-lg">
<span className="glyphicon glyphicon-search" aria-hidden="true"></span>
</button>
{/* ... */}
</form>
{/* ... */}
</div>
Now it's clear that we have a <form> with nested <label>, <input> and <button> elements.
We're done with creating our <form> for the moment. The next UI element is status message box - it tells you which address was found:
<div className="container">
{/* form */}
<div className="row">
<div className="col-sm-12">
<p className="bg-info">London, United Kingdom</p>
</div>
</div>
</div>
As you can see it's a simple <p> element with address in it. Nothing fancy at the moment.
Finally, let's create the map element:
<div className="container">
{/* form */}
<div className="row">
<div className="col-sm-12">
<p className="bg-info">London, United Kingdom</p>
<div className="map"></div>
</div>
</div>
</div>
Our map is an empty <div> element.
For now that's all we want our Application React component to render initially. But then who's going to create the actual map?
Google Maps JavaScript API.
It's their job to render the rest of the DOM that it needs for display the map. The next question you should ask yourself is: when should Google Maps JavaScript API start rendering the map?
Right after our Application component has finished mounting. React provides a lifecycle function that we can use to tell React what to do right after component finished mounting - componentDidMount():
componentDidMount: function () {
// tell Google Maps JavaScript API to create a map
},
How do we create a map using Google Maps JavaScript API?
First we need to select the DOM element that will act as a container for our map. I am sure you're already familiar with document.getElementById() or document.querySelector() or other DOM API functions that allow you to select elements from the DOM.
However React offers another way to access DOM elements by using refs and we're going to use the ref Callback Attribute.
Let's create setMapElementReference property on our component specification object and assign a function to it:
var Application = React.createClass({
componentDidMount: function () { /* ... */ },
setMapElementReference: function (mapElementReference) {
this.mapElement = mapElementReference;
},
render: function () { /* ... */ }
});
setMapElementReference() function will assign mapElementReference to mapElement property on our component specification object. Now we need to add ref attribute to our <div className="map"> element inside of render() function:
<div className="map" ref={this.setMapElementReference}></div>
We're assigning this.setMapElementReference to ref attribute. In this case our ref attribute is the Callback Attribute. Now React will call setMapElementReference() function immediately after the Application component is mounted. As a result the reference to <div className="map"> DOM element will be stored in mapElement property and now we can use it in our component whenever we need to reference our map element.
Where do we want to reference out map element? In componentDidMount(), because we want to render the map right after Application component is mounted:
componentDidMount: function () {
this.map = new google.maps.Map(this.mapElement, {
zoom: 8,
center: {
lat: 51.5085300,
lng: -0.1257400
}
});
},
Here we're calling new google.maps.Map() function to render the map. This function takes two parameters: 1) mapElement which references our map DOM element and 2) options object that sets zoom and center properties of the map. Latitude of 51.5085300 and longitude of -0.1257400 is a central point in London, United Kingdom.
Providing literal values like we did in the previous code snippet is a bad practice, so instead let's create a couple of new variables before we declare our React component:
var React = require('react');
var INITIAL_LOCATION = {
address: 'London, United Kingdom',
position: {
latitude: 51.5085300,
longitude: -0.1257400
}
};
var INITIAL_MAP_ZOOM_LEVEL = 8;
var ATLANTIC_OCEAN = {
latitude: 29.532804,
longitude: -55.491477
};
var Application = React.createClass({ /* ... * / });
INITIAL_LOCATION stores the initial location that we want to show on the map when user runs our application. INITIAL_MAP_ZOOM_LEVEL is the initial zoom level of our map. And finally we've created ATLANTIC_OCEAN variable that stores coordinates of Atlantic Ocean - this is what we want to show on our map when user searches for a non-existing address.
Now let's go back to componentDidMount() function and replace literal values with references:
componentDidMount: function () {
this.map = new google.maps.Map(this.mapElement, {
zoom: INITIAL_MAP_ZOOM_LEVEL,
center: {
lat: INITIAL_LOCATION.position.latitude,
lng: INITIAL_LOCATION.position.longitude
}
});
},
Notice that we're assigning our new map object to this.map. We're going to store the reference to the map object in our component specification object.
Let's review what's happening in our application at the moment:
The next step is to create a marker:
componentDidMount: function () {
this.map = new google.maps.Map(this.mapElement, {
zoom: INITIAL_MAP_ZOOM_LEVEL,
center: {
lat: INITIAL_LOCATION.position.latitude,
lng: INITIAL_LOCATION.position.longitude
}
});
this.marker = new google.maps.Marker({
map: this.map,
position: {
lat: INITIAL_LOCATION.position.latitude,
lng: INITIAL_LOCATION.position.longitude
}
});
},
Just like with the map object, we're assigning the reference to our new marker object to our component specification object.
Next we need to create a new geocoder object:
componentDidMount: function () {
this.map = new google.maps.Map(this.mapElement, {
zoom: INITIAL_MAP_ZOOM_LEVEL,
center: {
lat: INITIAL_LOCATION.position.latitude,
lng: INITIAL_LOCATION.position.longitude
}
});
this.marker = new google.maps.Marker({
map: this.map,
position: {
lat: INITIAL_LOCATION.position.latitude,
lng: INITIAL_LOCATION.position.longitude
}
});
this.geocoder = new google.maps.Geocoder();
},
Once again, we're assigning the reference to our new geocoder object to our component specification object.
Now right after component is mounted we do 3 things:
At this point, our application will display a map with a marker pinned in a center of London, United Kindom.
The next step is to make our search form work.
What happens when user submits the search form? We need to do a couple of things, but first let's create a new handleFormSubmit() function and assign it as a property to our component specification object:
handleFormSubmit: function (submitEvent) {/* ... */},
When should handleFormSubmit function be called? When user submits our search form. Let's go back to our <form> element and add onSubmit attribute:
<form className="form-inline" onSubmit={this.handleFormSubmit}>{/* ... */}</form>
What should handleFormSubmit function do?
3 things:
Let's start with preventing the default submit behaviour:
handleFormSubmit: function (submitEvent) {
submitEvent.preventDefault();
},
Next, we need to get the value that user typed into search input. How can we reference that input element? The same way we referenced our map element: using ref callback attribute.
Let's go back to our <input> element inside of render function and add a new ref attribute:
<input type="text"
className="form-control input-lg"
id="address"
placeholder="London, United Kingdom"
ref={this.setSearchInputElementReference}
required />
We're assigning this.setSearchInputElementReference to ref attribute. Let's create this.setSearchInputElementReference property on our component specification object and assign a new function to it:
setSearchInputElementReference: function (inputReference) {
this.searchInputElement = inputReference;
},
Just like with the reference to a map element, we're assigning a reference to our input element to a property called searchInputElement on our component specification object, so that we could reference it from anywhere in our component.
Now when we have a way to reference our search input element, let's get it's value in handleFormSubmit function:
handleFormSubmit: function (submitEvent) {
submitEvent.preventDefault();
var address = this.searchInputElement.value;
},
We're assigning that value to a new address variable, because it will be an address string that user has typed into the search box.
Finally, we want to geocode that address string:
handleFormSubmit: function (submitEvent) {
submitEvent.preventDefault();
var address = this.searchInputElement.value;
this.geocodeAddress(address);
},
Here we're calling geocodeAddress function and passing address as an argument.
Now let's create geocodeAddress function as a property of our component specification object:
geocodeAddress: function (address) {
this.geocoder.geocode({ 'address': address }, function handleResults(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
this.map.setCenter(results[0].geometry.location);
this.marker.setPosition(results[0].geometry.location);
return;
}
this.map.setCenter({
lat: ATLANTIC_OCEAN.latitude,
lng: ATLANTIC_OCEAN.longitude
});
this.marker.setPosition({
lat: ATLANTIC_OCEAN.latitude,
lng: ATLANTIC_OCEAN.longitude
});
}.bind(this));
},
Here we're calling geocode function on geocoder object that we've created earlier. geocode function takes two arguments: 1) request object and 2) callback function.
Our request object is very simple: { 'address': address } - it only has one property: the address that we want to geocode.
Our callback function is handleResults - it takes two parameters: 1) array of geocoder result objects and a geocoder status object.
Let's discuss the logic behind our handleResults function.
It needs to handle 2 cases:
We first handle our first case by writing this if statement:
if (status === google.maps.GeocoderStatus.OK) {
// response contains a valid geocoder result object
}
Inside of that if statement, we want to do 2 things:
if (status === google.maps.GeocoderStatus.OK) {
this.map.setCenter(results[0].geometry.location);
this.marker.setPosition(results[0].geometry.location);
return;
}
results[0].geometry.location is the location object of the first geocoder result object. It has coordinates that match best the address provided by our user. We pass it as an argument to map.setCenter and marker.setPosition function calls in order to change center of the map and position of the marker.
That's all we want to do in that case, so we add return statement to terminate our handleResults function.
This will cover our first case or success case when response contains a valid geocoder result object.
How should we handle our second case or a failure case when we didn't get a valid geocoder result object?
Remember we've created ATLANTIC_OCEAN variable that references object with the coordinates of Atlantic Ocean? This is the case when we need that object - we want to show Atlantic Ocean when user provides invalid address:
geocodeAddress: function (address) {
this.geocoder.geocode({ 'address': address }, function handleResults(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
this.map.setCenter(results[0].geometry.location);
this.marker.setPosition(results[0].geometry.location);
return;
}
this.map.setCenter({
lat: ATLANTIC_OCEAN.latitude,
lng: ATLANTIC_OCEAN.longitude
});
this.marker.setPosition({
lat: ATLANTIC_OCEAN.latitude,
lng: ATLANTIC_OCEAN.longitude
});
}.bind(this));
},
After if statement, we set center of the map and position of the marker to Atlantic Ocean. These two function calls will be executed only if there is no valid geocoder result object.
Now our map and marker will update when user submits a valid address. Our search interacts with our map.
But what about the other UI element in our app - status? It's purpose is to render address text that is displayed on the map.
Let's build it!
Right now it renders static string London, United Kingdom:
<p className="bg-info">London, United Kingdom</p>
That string needs to be dynamic. And so it's time for our React component to become stateful.
Let's create getInitialState property on our component specification object and assign a function to it:
var Application = React.createClass({
getInitialState: function () {
return {
isGeocodingError: false,
foundAddress: INITIAL_LOCATION.address
};
},
geocodeAddress: function (address) { /* ... */ },
handleFormSubmit: function (submitEvent) { /* ... */ },
componentDidMount: function () { /* ... */ },
setSearchInputElementReference: function (inputReference) { /* ... */ },
setMapElementReference: function (mapElementReference) { /* ... */ },
render: function () { /* ... */ }
});
The initial state object for our component will have two properties:
isGeocodingError set to false.foundAddress set to INITIAL_LOCATION.addressisGeocodingError flags if we didn't get a valid geocoder result object. If that's the case, then we need to render an error message.
foundAddress stores the address string that was returned by our geocoder.
Next, inside of our render function, let's replace this:
<p className="bg-info">London, United Kingdom</p>
With that:
{
this.state.isGeocodingError
?
<p className="bg-danger">Address not found.</p>
:
<p className="bg-info">{this.state.foundAddress}</p>
}
Here we're checking if this.state.isGeocodingError is truthy, and in that case we're rendering an error message, otherwise we're rendering address string from this.state.foundAddress.
When should we change our component's state?
Let's review our geocodeAddress function:
geocodeAddress: function (address) {
this.geocoder.geocode({ 'address': address }, function handleResults(results, status) {
if (status === google.maps.GeocoderStatus.OK) {
this.setState({
foundAddress: results[0].formatted_address,
isGeocodingError: false
});
this.map.setCenter(results[0].geometry.location);
this.marker.setPosition(results[0].geometry.location);
return;
}
this.setState({
foundAddress: null,
isGeocodingError: true
});
this.map.setCenter({
lat: ATLANTIC_OCEAN.latitude,
lng: ATLANTIC_OCEAN.longitude
});
this.marker.setPosition({
lat: ATLANTIC_OCEAN.latitude,
lng: ATLANTIC_OCEAN.longitude
});
}.bind(this));
},
Here in both cases we're now changing component's state.
When response contains a valid geocoder result object, we're assigning new address string from results[0].formatted_address property and changing isGeocodingError to false:
this.setState({
foundAddress: results[0].formatted_address,
isGeocodingError: false
});
This allows us to display the exact address string that geocoder found and remove any error messages that we could potentially display previously.
On the other hand, when we the response doesn't contain a valid geocoder result object, we're assigning null to foundAddress and true to isGeocodingError:
this.setState({
foundAddress: null,
isGeocodingError: true
});
This state communicates the fact that no address found and there is no address string.
Now our application can gracefully handle both cases: success and failure.
And that's all folks!
Please take a look at the complete source code on GitHub and the live version of our app.
I hope you've enjoyed this tutorial and I would love to hear your feedback in the comments. You can get in touch with me via Twitter and email.

P.S. I've also written React.js Essentials book and I teach people React.js and JavaScript!