Tag Archives: google maps

React Components for Google Maps – Part 2

Making React and Google Maps play nice with each other is not a complex task, provided you have some insight in how to open seams in the google maps layer to allow it to interoperate with other libraries. I have learned this from the school of hard knocks, so I hope to shed some light on the tricky parts to save you some time. If you haven’t already, I recommend you read part 1 of this series for some important background information.

Let’s dig into the implementation. I have broken out the examples into a set of gists. If you don’t care to follow through each step, you can jump straight to the full example of the OverlayView.

Implementing the OverlayView

There are 3 high level requirements for creating a custom OverlayView. The module must:

  1. Prototypically inherit from the google.maps.OverlayView class
  2. Implement a onAdd method, which will be called once the map has been set for the overlay
  3. Implement a draw method, which will be called each time the map is zoomed

Secret sauce

The secret sauce to getting the overlay and React to play nice is to take the DOM element representing the content of the overlay as a parameter through the constructor. As we will see later on, this will allow us to handle rendering the content of the overlay in the React component, but position it on the map through the overlay.

Extend the google.maps.OverlayView class

Adding the overlay to the DOM

The onAdd method is the point at which the overlay can insert itself into the DOM. This is achieved by using the getPanes method, which is inherited from the parent OverlayView class. There are multiple panes, but for our particular case, we’ll use the overlayLayer pane. This will allow us to control any click-like behavior completely within the DOM structure of the overlay.

It’s important to understand the purpose of this method when integrating an overlay with a particular framework or library, because typically inserting a particular view into the DOM is often handled by the framework. This method is our seam that allows us to inject content into the map without having that content coupled to the map itself.

Positioning the overlay on the map

The draw method is invoked when the overlay needs to calculate its position on the map. In the simplest case, an overlay is positioned by a single lat/lng point, however, it is also possible to position the overlay by a bounds as well. We’ll focus on the simplest case in this article. In order to translate a lat/lng to a pixel, we need to get a reference to the projection of the overlay. Projections are google maps’ way of turning lat/lng points into pixels relative to something within the map structure.

To safely get a reference to a projection, we have to wait for the map instance to be idle. I have found it is best to handle this dependency outside the overlay, particularly if you plan on having to render hundreds or thousands of overlays on the map. I will discuss how to implement handling this externally as part of implementing the facade. Regardless of where it is handled, the way to accomplish knowing when the map is idle is by adding a listener to the map instance for the idle event.

Performance

From my experience, when rendering a high volume of overlays on the map (300+), the draw method has the highest likelihood to impact rendering performance. There are two reasons for this. First of all, relatively speaking, it is expensive to determine the dimensions of an element in the DOM. We need the dimensions of the overlay in order to properly position it on the map. Secondly, the draw method will be invoked each time we zoom, therefore, we will need the dimensions of our overlay again in order to correctly position it.

When rendering high volumes of overlays, we can dramatically improve performance by caching the dimensions. To take it a step further, if your overlays all have a consistent height and width, you can calculate the dimensions once and use the cached copy for all other instances. Exactly how to achieve that is outside the scope of this article, but I wanted to highlight it, because it has caused me a great deal of heartburn in the past.

Implementing the facade

Our general requirements for the facade are:

  1. Handle the creation of the google map instance (a factory)
  2. Provide a facility for consumers to know when the map instance is idle without having to add listeners themselves
  3. Provide a factory for creating our custom overlays

Map facade

The best way I have found to handle both items 1 and 2 is to define a method that will handle creating the map instance, but returns a Promise. The Promise is resolved once the map is idle, and should resolve with the newly created map instance. This allows consumers to bind to the Promise and to perform any actions that are dependent upon the map being idle. As consumers, we don’t care if we bind to the map early or late in its lifecycle, once it’s idle, we know our callback will be invoked and we’ll have access to an instance of the map.

Overlay factory

To avoid coupling ourselves to our custom overlay, it is best to implement a factory to handle creating instances of the overlay for us. This allows us to handle the setup of the overlay in a single place, and will improve the testability of modules that need to create an overlay instance.

Implementing the React component

By abstracting away the details of defining the overlay and its interactions with the map in our facade, there is very little overlay-specific logic that needs to be included in the component. All we need to do is call our factory. The key is to do this as part of the componentDidMount method of the component, since this is the point in time where we will have access to the DOM node of the component. You are free to handle the rendering of the content for the overlay in the render method of the component.

Secret sauce

The key to achieving this is to render the overlay component with a detached div element and not an element that is already in the DOM. This allows us to render our content in the detached element and to pass it to the overlay. The component can then safely share it’s DOM node with the overlay. Here’s an example of how to achieve this with my idle-maps module.

Parting Thoughts

Where I work, we have a very map-centric application where, at times, we render 2,000+ overlays. We also have a variety of types of overlays that we leverage. Following the patterns I have laid out has proven to be very effective for us. While building out this capability within our application, it would have been quite valuable to have known some of these patterns early on. I hope by documenting them here, I can save another developer some time and pain going down this path.

React Components for Google Maps – Part 1

Building React components for use with google maps can present some challenging problems for those who aren’t aware of the basics/quirks of developing with google maps. I aim to shed some light on the fundamentals of how to get the two to play nicely together, and to encourage developing the components in a way that will promote testability. As always, feel free to jump right into the code if you don’t need the TL;DR description of how it all works.

As you’ll notice in the example, I have created two npm modules to support this:

  • async-google-maps – provides a facade for asynchronously creating google map instances as well as a base overlay views that are positioned by a lat/lng
  • idle-maps – builds on top of the async-google-maps module, by providing a set of React components for creating google map instances and overlay content

Why?

The standard google maps modules are fine for simple use cases, such as displaying a marker for a given Lat/Lng point or InfoWindows for descriptive content about a given point. However, things get interesting when you need to be able to have full control over the style of a component, or to easily update content based on changes from the server. There are third-party libraries out there that will wrap the google maps primitives, but those have their own problems and often contain a bunch of bloat trying to handle all possible user interactions and browser quirks.

Prerequisites

  1. A basic understanding of spatial data primitives: coordinate systems, projections, basic geometric data types
  2. A basic understanding of React
  3. A basic understanding of google maps
  4. A basic understanding of browserify
  5. A google maps API key

Where we’re going

As an introductory step, we will start by building a custom OverlayView that is positioned by a point (as opposed to being positioned by a bounds, which is also possible). We will keep this simple by focusing on the basics using React with google maps and avoid fetching data from the server or binding UI interactions to a client-side router. We’ll cover those in later articles.

Separating concerns

My preferred method for separating concerns when building custom OverlayViews is to leverage the following pattern:

  • Build a constructor function which inherits from the google.maps.OverlayView constructor.
  • Implement a facade, (in the spirit of the Command Pattern) which abstracts away the complexity of the coordination that needs to occur between the OverlayView and the React component
  • Implement the React component in such a way that it knows nothing about any of the google maps APIs

What’s next?

In the next article, we will take a deep dive into the implementation and highlight a few of the pitfalls I discovered while working on this myself.