DEV Community

JPStupfel
JPStupfel

Posted on

Build Google Maps Search Component in React (Part Two)

In the previous post we left off with a fully functioning google map in our react application. In case you missed that one, here is a link build-google-maps-search-component-in-react-part-one. Make sure to work through Part One as we will be utilizing the components built during that blog.

In this post we are going to build a search bar that can search for an address or location and recenter our view on that location within the map component we created earlier.

Install the DOTENV Gem.

Install DOTENV by adding the following code to your gem file: gem 'dotenv-rails'

Then run bundle install in your terminal. This gem will be needed when ascribing our API key to an environment file in our Ruby backend.

Create a Google Maps Geocoding API Key.

Navigate to The Google Maps API Homepage, sign in and select the project you created in the last blog post linked above. After that, click on Credentials, and at the top of the screen select "create credentials".

Next, click on the new API key to see the options for that key. Under the options for API restrictions, opt to "restrict key" and select from the drop down menu only the Geocoding API.

Once that is done, accept the options you have specified and navigate to the "Quotas" tab on the left sidebar. Select 'Geocoding API' from the drop down menu at the top and place a restriction on every type of call possible from that API. Personally, I set my limit to 500 calls per day and had no trouble staying below this during development. As long as your quotas are below the free credit Google provides, you should be safe from charge. Please keep in mind that I am no expert in API security. I am merely sharing the steps I took. Google has extensive documentation about other security features you may implement that I won't cover in this blog.

Conceal Your New API KEY as an Environment Variable

If you remember from our last post, when you call your API in your React App, even if that API is hidden in an environment file, anyone can grab your API key by looking at your source code. Unlike in the previous post where Google Maps JavaScript API requires that you call the API in the front-end of the application, when dealing with the Geocoding API you actually need to make your API call from the server-side in order for your key to remain safe. This is because Google does not allow you to restrict the Geocoding API to HTTP referrers as we did for the Google Maps JavaScript API key. As such, we will need to make any call to our Geocoding API via our Rails back-end and then serve that information to our client-side React Application. That being said, we definitely do not want to push our secret API key to GitHub. So let's go ahead and make a .env file where we can hide our API on our Rails -back-end. This process is similar to the protocol explored in Part One of this series, except that now we are creating everything in our Rails App.

Create a file in your Rails root directory called ".env".

In that file, write API_KEY=Adksdafsdihaposafsdsafdsdkdfi12343;hasiodfho4 except use your own special API key.

Then, to test that this worked, open your Rails console by typing rails c in your terminal window. Then, in the console type ENV['API_KEY'] and hit ENTER. Your secret API Key should be returned.

Go ahead and add that .env file to our Rails App's .gitignore file by adding .env to the last line in '/.gitignore'.

Now you can call that API key from anywhere in your Rails App and it will never be pushed to GitHub.

If you remember from the Part One, we will have to declare the environment variable directly in our deployment server as well. For example, if using Heroku, in the command line, cd into your rails app directory and run heroku config:set API_KEY= asdfasfinalkdsfasdfsdi2513453. This essentially accomplishes the same thing as the .env file, but you are stating the value of that environment variable directly in Heroku.

Call the API from your Rails Controller

Remember that we want to make our actual API call from within our rails back-end to avoid React including our API key in the html it compiles when our app goes live where hackers could easily steal it. The idea here is to make our own fetch request from our front-end to our back-end. That request will look very similar to the request we will ultimately send to the Google Maps Geocoding API. The only difference will be that we will not be including the API key in the params of this initial front-end to back-end request. Once our back-end receives the request, we will take the params from our front-end, in this case an address or location, and make the official Google Geocoding API request from our rails controller and include our API Key.

Create a controller

In your terminal, run rails g controller geocoding. This will create a controller where we will handle the logic for this request.

Then, within the newly created './controllers/geocoding_controller.rb' let's add a private method that handles the Google Maps Geocoding API call:

require 'uri'
require 'net/http'

class GeocodingController < ApplicationController

    private

    def getCoords address
        uri = URI("https://maps.googleapis.com/maps/api/geocode/json?address='#{address}'&key=#{ENV['API_KEY']}")
        res = Net::HTTP.get_response(uri)
        return JSON(res.body)['results'][0]['geometry']['location']
    end
end

Enter fullscreen mode Exit fullscreen mode

Let's look at what this code is doing. We are creating a private method that accepts an address as an argument. Then we are making use of the ruby URI module to make our fetch request to google. Note we have to require both the 'uri' and 'net/http' in the top line of our controller. Documentation for the ruby URI module can be found here. Also notice that we are using string interpolation to include both the address argument and our API_KEY environment variable in the url of the fetch request such that both will be passed as params to the Google Maps geocoding API.

Next we need to add the actual controller method we will reference in our routes file. Let's add it above the private methods in our './controllers/geocoding_controller.rb' file:

require 'uri'
require 'net/http'

class GeocodingController < ApplicationController

    def getAddress
        location = getCoords(params['address'])
        render json: location, status: 200
     end

    private
    def getCoords address
        uri = URI("https://maps.googleapis.com/maps/api/geocode/json?address='#{address}'&key=#{ENV['API_KEY']}")
        res = Net::HTTP.get_response(uri)
        return JSON(res.body)['results'][0]['geometry']['location']
    end
end

Enter fullscreen mode Exit fullscreen mode

The logic here is pretty self explanatory. We will call our private getCoords method using params['address'] from our front-end.

Create a Route for the Client-Side Request

One more step to get the logic for this request set up on the back-end is to create a route for the API call in our routes.rb file.

Let's modify our './config/routes.rb' file to look like this:

Rails.application.routes.draw do

  # Defines the root path route ("/")
  # root "articles#index"

  get "/geocoding/:address", to: 'geocoding#getAddress'

end
Enter fullscreen mode Exit fullscreen mode

As you can see, when our back-end receives a get request at the path "/geocoding/:address" rails will return the properties of the method "getAddress" from our geocoding controller.

Let's go ahead and test this out. In your terminal run rails s -p 4000 to start your server on port 4000 and navigate to 'http://localhost:4000/'. You should see the typical rails landing page. Next, let's try to navigate to 'http://localhost:4000/geocoding/Barcelona'. You should see returned on the page in json format the latitude and longitude of Barcelona, Spain.

Image description

Note that google handles the search interpolation here, so the google will make its best guess on which coordinates to return based on your search parameters. If you navigate to 'http://localhost:4000/geocoding/Madrid' google will give you the coordinates for Madrid, Spain, not Madrid, New Mexico, United States.

Set up cors to allow calls from react app.

This is a pretty standard problem faced when building a react front-end and rails back-end. Basically, rails won't let you communicate from another server (e.g. react running on localhost:3000 and rails on localhost:4000) because of the default cors settings. To disable this we are going to make use of a gem called "rack-cors". Add gem 'rack-cors', :require => 'rack/cors' to your gem file. Close your rails server. Run bundle install.

Next, in your ./config/application.rb file add the following code to run within the Application class

config.middleware.insert_before 0, Rack::Cors do
      allow do
        origins '*'
        resource '*', :headers => :any, :methods => [:get, :post, :options]
      end
    end
Enter fullscreen mode Exit fullscreen mode

Then restart your server and we should be ready to make request from our front-end to our back-end.

Fetch the coordinates from your React App.

As mentioned before, this is Part Two of a two part series. In Part One of this series we built a basic google map component using the Google Map Javascript API. We will now be adding a search bar to that component based on this back-end logic we created above.

Let's make two changes to our './client/src/components/MapContainer.jsx' file. Firstly, let's create a constant called "mapCenter" to be a piece of stateful logic within react that will tell the map where to center its window. This includes three steps: 1. import the useState hook in the top line of your code. 2. create the stateful variable called mapCenter, 3. set the "Center" prop on your GoogleMap component to mapCenter.

import React, {useState} from 'react';

//import the needed components from the Google Maps API
import { GoogleMap, LoadScript} from '@react-google-maps/api';

//refer the map API Key from the env file
const API_KEY = process.env.REACT_APP_API_KEY;

//Create the React Component Function
function MapContainer(){


//These are default map styles so your map is visible
const mapStyles = {        
    height: "50vh",
    width: "100%"}; 


//Stateful logic for map center set to a default lat and lgn
const [mapCenter, setMapCenter] = useState({lat: 35, lng: -105})


  return (
     <LoadScript
            googleMapsApiKey= {API_KEY} >
        <GoogleMap
          mapContainerStyle={mapStyles}
          zoom={13}
          center={mapCenter}
        >
         </GoogleMap>
     </LoadScript>
  )
}
export default MapContainer;
Enter fullscreen mode Exit fullscreen mode

Next up, let's place a controlled form in our MapContainer component that will serve as our search bar. Note that because this is a controlled form we will need another piece of stateful logic which we will call 'address', and we will set the input tag to have an onChange event of updating the state variable "address".

import React, {useState} from 'react';

//import the needed components from the Google Maps API
import { GoogleMap, LoadScript} from '@react-google-maps/api';

//refer the map API Key from the env file
const API_KEY = process.env.REACT_APP_API_KEY;

//Create the React Component Function
function MapContainer(){


//These are default map styles so your map is visible
const mapStyles = {        
    height: "50vh",
    width: "100%"}; 


//Stateful logic for map center set to a default lat and lgn
const [mapCenter, setMapCenter] = useState({lat: 35, lng: -105})

//Stateful logic for the search parameter
const [address, setAddress] = useState('')

  return (

     <LoadScript
            googleMapsApiKey= {API_KEY} >
              <form>
                <input
                    type="text"
                    placeholder="Customized your placeholder"
                    value={address}
                    onChange={(event)=>setAddress(event.target.value)}
                />
              </form>
              <GoogleMap
                mapContainerStyle={mapStyles}
                zoom={13}
                center={mapCenter}
              >
              </GoogleMap>

     </LoadScript>
  )
}
export default MapContainer;
Enter fullscreen mode Exit fullscreen mode

Finally, let's add our helper functions that will make the actual API call when the form is submitted. This requires several steps: 1. Define a helper function as 'getCoordinates' that makes the API call and sets our mapCenter state variable to the returned coordinates. 2. Define a helper function "onSubmit" that will handle the submit event on our form. 3. Make sure we prevent default behavior when we submit the form. 4. Make sure we are calling getCoordinates when we submit the form.

import React, {useState} from 'react';

//import the needed components from the Google Maps API
import { GoogleMap, LoadScript} from '@react-google-maps/api';

//refer the map API Key from the env file
const API_KEY = process.env.REACT_APP_API_KEY;

//Create the React Component Function
function MapContainer(){


//These are default map styles so your map is visible
const mapStyles = {        
    height: "50vh",
    width: "100%"}; 


//Stateful logic for map center set to a default lat and lgn
const [mapCenter, setMapCenter] = useState({lat: 35, lng: -105})

//Stateful logic for the search parameter
const [address, setAddress] = useState('')

  //takes address and sets coords to coordinates
  //be sure to specify the port where your rails server is running
function getCoordinates(address){
    fetch(`http://localhost:4000/geocoding/${address}`)
      .then(response => response.json())
      .then(data =>{ setMapCenter(data);
      })  
  }

  function handleSubmit(event){
    event.preventDefault()
    getCoordinates(event.target.firstChild.value)

  }

  return (

     <LoadScript
            googleMapsApiKey= {API_KEY} >
              <form
              onSubmit={handleSubmit}>
                <input
                    type="text"
                    placeholder="Customized your placeholder"
                    value={address}
                    onChange={(event)=>setAddress(event.target.value)}
                />
              </form>
              <GoogleMap
                mapContainerStyle={mapStyles}
                zoom={13}
                center={mapCenter}
              >
              </GoogleMap>

     </LoadScript>
  )
}
export default MapContainer;
Enter fullscreen mode Exit fullscreen mode

Now, if we look at our React App, we can type "Barcelona" in our input field, hit enter, and our map re-centers over Barcelona Spain.

Image description

That just about covers it for this section. If you enjoyed this series please like and share and please checkout some of the other blogs I've done. Happy coding!

Top comments (0)