Google Maps + React

 

 

UPDATE: There is newer post about this that discusses using Google Maps and React with ES6. There is also now a public repo up on github that contains the version from the more recent article: https://github.com/Dacello/react-google-maps


 

 

At Revelry, we follow the MVC pattern to build web apps. In most cases we use Rails for the Models and Controllers, and React.js for the Views. If you aren’t familiar with React.js, check out what you need to know about React before reading on. Essentially, React uses a component-based approach to building JavaScript-driven UIs. With React, all of our view templates are comprised of these JavaScript (well, actually JSX) components.

Recently I’ve worked on a few projects where we had to build customized map components using the Google Maps JavaScript API v3. Overall, working with the GMaps API is pretty nice. Since it is, after all, written and maintained by Google, it’s extensively documented and reliable. Only thing is, Google hasn’t yet adapted to cutting edge client-side frameworks such as React. It is possible put a Google Map in a React component, but unfortunately it just doesn’t yet work the React way.

What Google wants you to do

Simple example directly from the Google Maps API Tutorial:

<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      html, body, #map_canvas { height: 100%; margin: 0; padding: 0;}
    </style>
    <script type="text/javascript"
      src="https://maps.googleapis.com/maps/api/js?key=API_KEY">
    </script>
    <script type="text/javascript">
      function initialize() {
        var mapOptions = {
          center: { lat: -34.397, lng: 150.644},
          zoom: 8
        };
        var map = new google.maps.Map(document.getElementById('map_canvas'),
            mapOptions);
      }
      google.maps.event.addDomListener(window, 'load', initialize);
    </script>
  </head>
  <body>
    <div id="map_canvas"></div>
  </body>
</html>

What React Doesn’t want you to do

google.maps.event.addDomListener(window, 'load', initialize);

The window is not within the scope of this component. In Reactland, if you ever need to manipulate the DOM, it should be done inside a component, and to an element contained within said component. In this case we don’t really need to manipulate the DOM, the example is just doing that run the create map function after the page loads.

In React we can do this using componentDidMount, which is called after the component has been rendered. We have to wait for the component to be rendered because Google requires you to manipulate the DOM, which is non existent before the component is rendered (or the page is loaded).

This is not ideal because it cancels out the performance advantage of React. The result is that most of the page loads nice and fast, but the map gets rendered slowly, since it has to wait for the component to render before hitting the google maps api to render the map.

The other non-reacty code which google suggests:

google.maps.Map(document.getElementById('map_canvas'), mapOptions);

Google suggests that you get your DOM element via document.getElementById('map_canvas'). Similarly to using window above, document is out of scope of this component. Another thing is that in Reactland, instead of using jQuery or document.getElementById, you are supposed to use Refs. Refs are easy. Just add ref="map_canvas" to the element, and then then access it throughout the component with this.refs.map_canvas.getDOMNode().

GMap React Component

This is a very simplified version of a GMap component of ours. This assumes you only want one marker and one infoWindow, and you are passing in the coordinates to the component’s props. Notice the filetype is cjsx, which is JSX, just in CoffeeScript.

#app/assets/javascripts/components/GMap.js.cjsx
App.Components.GMap = React.createClass
  map: null
  marker: null
  infoWindow: null

  render: ->
    <div className="GMap">
      <div ref="map_canvas">
      </div>
    </div>

  componentDidMount: ->
    # create the map, marker and infoWindow after the component has
    # been rendered because we need to manipulate the DOM for Google =(
    @map = @createMap()
    @marker = @createMarker()
    @infoWindow = @createInfoWindow()

    # have to define google maps event listeners here too
    # because we can't add listeners on the map until its created
    google.maps.event.addListener @map, 'zoom_changed', => @handleZoomChange()
    google.maps.event.addListener @map, 'dragend', => @handleDragEnd()

  createMap: ->
    coords = @props.coords
    mapOptions =
      minZoom: 9
      zoom: 10
      center: new google.maps.LatLng(@props.coords.lat, @props.coords.lon)

    new google.maps.Map(@refs.map_canvas.getDOMNode(), mapOptions)

  createMarker: ->
    marker = new google.maps.Marker
      position: new google.maps.LatLng(@props.coords.lat, @props.coords.lon)
      map: @map

  createInfoWindow: ->
    contentString = "<div class='InfoWindow'>I'm a Window that contains Info Yay</div>"
    infoWindow = new google.maps.InfoWindow
      map: @map
      anchor: @marker
      content: contentString

  handleZoomChange: ->
    # something happens when the zoom changes
    # this is where it's handled

  handleDragEnd: ->
    # something else happens when the map is dragged somewhere
    # this is where that's handled

Love making great software? Let’s talk!

Apply to work with us!

Let’s get to know each other.
Meet the team or check out more Development articles.
Keep in touch by subscribing to CODING CREATIVITY.

More Posts by Daniel Andrews: