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
:
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:
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:
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:
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:
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()
:
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:
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:
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:
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:
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:
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:
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:
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:
When should handleFormSubmit
function be called? When user submits our search form. Let's go back to our <form>
element and add onSubmit
attribute:
What should handleFormSubmit
function do?
3 things:
Let's start with preventing the default submit behaviour:
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:
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:
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:
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:
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:
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:
Inside of that if
statement, we want to do 2 things:
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:
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
:
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:
The initial state object for our component will have two properties:
isGeocodingError
set to false
.foundAddress
set to INITIAL_LOCATION.address
isGeocodingError
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:
With that:
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:
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 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 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!