Published May 11, 2016Last updated Jan 15, 2017

Integrating Google Maps in React Using refs

Integrating Google Maps in React Using refs

The content of this tutorial is extracted from a React Live Class given by Thomas Tuts.


Introduction

React's ref provides an easy way to access DOM nodes and components, and to integrate third-party libraries in our React components. Let's take a brief look at how we can use React's ref attribute to hook up our component with a basic Google Maps integration. As an exercise, we'll render a map using Google Maps that starts on the Eiffel Tower, and has a button that pans us all the way to the Arc de Triomphe. Simple stuff, but it should be enough to get you up and running with using refs. Of course, feel free to modify things as you see fit!

Here's a demo of the end product:

We're going to write our code in ES2015, so some familiarity with its syntax is required. To avoid having to set up a whole build system, we'll use Codepen instead.

What's a ref?

ref is a special attribute in React that you can use to store a reference to that component or DOM node. You can then access that React component (or DOM node) and do things with it. Keep in mind that when you attach a ref to a DOM node, you get access to that node, whereas a ref on a React component, will get you a reference to the component instance instead. The ref attribute takes either a function or a string, which is passed in much like you would pass in a prop.

When you pass in a function, that function gets executed when the component is mounted and unmounted. You can then do stuff with the component, or store it for later use.

When you pass in a string instead, you can access the component using this.refs.refname. To keep things simple, we'll use the string method of accessing our ref.

Why you would use ref

Suppose you want to add a custom scrollbar to your fancy looking component. To do so, you might use something like perfect-scrollbar. If you check out the documentation, you'll notice that we need to pass it a DOM element so it can initialize and add extra DOM elements to provide the custom scrollbar:

var container = document.getElementById('container');
Ps.initialize(container);

We can use a ref here to pass to Ps.initialize().

As mentioned before, you can also use ref to access a React component. You can then access that component instance in its parent. For example, this could be handy to call a method on a child component to clear an input field after the parent component has finished processing the passed data. You could also use state for this, but this solution is a little cleaner.

Exercise: Google Maps

You can find the basic setup for the exercise on Codepen.

When you open the page, you'll most likely get a Google Maps error. That's because we need to add our own API key first. Let's get started!

Adding the Google API key

To get a key for the Google Maps JS API, follow these instructions. Once you have your key, plug it into the Codepen exercise by clicking the cog icon next to JS.

In the modal, you'll see a list of all our externally loaded dependencies (React, ReactDOM and Google Maps JS API). In the last one that says https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap, replace YOUR_API_KEY with the one that you just generated.

Rendering the initial map

Now that our Google Maps has been hooked up with an API key, let's take it for a spin. To access the DOM node to render our map into, we'll need to add a ref first:

// In the Map's render() method
return (
  <div ref="map" style={mapStyle} ref="map">I should be a map!</div>
);

Now that we can access our ref, we'll use the componentDidMount() lifecycle method to instantiate the map:

componentDidMount() {
  this.map = new google.maps.Map(this.refs.map, {
    center: EIFFEL_TOWER_POSITION,
    zoom: 16
  });
}

Why do we use componentDidMount()? If we used componentWillMount(), we would not have access to our refs yet, because at that point, our component doesn't exist yet, but it will. When it has mounted (i.e. componentDidMount(), we do have access to the DOM representation of our component, and we can access our ref. This stuff should not go into render() either, because render() can get called multiple times in a component's lifecycle (recreating the map every time, losing your position).

Even more important: a render() method should be pure and free from any side effects! Side effects are things like making an XHR request, modifying variables outside of the function's scope, writing data, manipulating the DOM, and so on. Render should only be concerned with returning a representation of the component based on its current props and state. That's all!

Panning to the Arc De Triomphe

Lastly, let's add a button that pans the map to the Arc De Triomphe. First, we'll add a button to our component, along with a click handler:

return (
  <div>
    <button onClick={this.panToArcDeTriomphe}>Go to Arc De Triomphe</button>
    <div ref="map" style={mapStyle}>I should be a map!</div>
  </div>
);

You might have noticed that I am not returning not just the map <div>, but rather one surrounding <div> that contains both the map and the button. This is because the render() method can only return one child element, that optionally has children of its own.

To add the click handler logic, we first need to know how we can achieve panning in the Google Maps JS API. We can use the Google Maps JS API Reference to find this out. Luckily, there's a .panTo() method that takes a latLng pair, much like the one we used to instantiate the map. Let's add the method to pan to the desired location:

panToArcDeTriomphe() {
  this.map.panTo(ARC_DE_TRIOMPHE_POSITION);
}

If you click the button, you'll notice that the map doesn't pan yet. That is because React ES2015 components do not autobind your methods for you. In the .panToArcDeTriomphe() method, we don't have access to the right scope, and cannot access this.map. To fix this, you can use .bind() to create a new function that does have the right scope. This can be done either when defining the click handler, or in your constructor. I prefer placing the .bind() stuff in the constructor since it's a little cleaner:

constructor() {
  super();
  this.panToArcDeTriomphe = this.panToArcDeTriomphe.bind(this);
}

If you click the button now, it should pan your map to the right location. If you feel like you missed something or have run into any errors, check out the solution on Codepen if you need a nudge in the right direction.


Found this tutorial helpful? Be sure to sign up for Codementor's React Live Class where you can learn React with a live instructor! You can also find Thomas's original blog post here.

Handling AJAX in your React Application with Agility
5 Essential React.js Interview Questions
React Beginner Tutorial: Building a Board Game from Scratch