Render a react component in google map canvas.React Portals.
Tags: react, context, portal, googlemap
Published: 2019-09-28 00:49:21








---
title: Render a react component in google map canvas.React Portals.
published: true
description: How to render a react component as a popup on google map. A perfect use case for React portal.
cover_image: https://thepracticaldev.s3.amazonaws.com/i/l2ps9weh2w9knwh4zocs.jpeg
tags: reactjs, context, portal, google-map
---
# What is this article about ?
How to render a react component as a popup on google map: a perfect use case for React portal. I'll briefly describe the drawing api provided by google map javascript library and then talk about how I integrated this 3rd party library with react.
##### _PS. Some implementation details have been removed from the code blocks to improve readability. The complete working example can be found on the corresponding codepen links._

### About React Portals
~~React Portals is this shiny new API from react~~ .. not really. React introduces this api in [v16.0](https://reactjs.org/blog/2017/09/26/react-v16.0.html#portals) and there are tons of articles with very elaborate and interesting explanations about what it is all about. So won't be talking much about it here. Just google react portal, or check these out..
[https://css-tricks.com/using-react-portals-to-render-children-outside-the-dom-hierarchy](https://css-tricks.com/using-react-portals-to-render-children-outside-the-dom-hierarchy)
[https://programmingwithmosh.com/javascript/using-react-portals/](https://programmingwithmosh.com/javascript/using-react-portals/)
[https://codeburst.io/reacts-portals-in-3-minutes-9b2efb74e9a9](https://codeburst.io/reacts-portals-in-3-minutes-9b2efb74e9a9)


### About Google maps drawing api.
_Skip this section if already familiar with the google maps api._

Google Maps library provides apis to draw any custom HTML content on the map. [InfoWindow](https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/infowindows)  is one of the popular apis. Another less known option is the  [OverlayView](https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-popup) class. In general InfoWindow apis are easy to use so implementations are much faster, but the popup markup is not fully customizable. 
In one of my previous projects in react, I wanted to render a react component in the map as an overlay and found that the [OverlayView interface](https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/reference/overlay-view#OverlayView) allows to plug-in any HTML element/markup on the map. This write-up will focus on this `OverlayView` class, although, the concepts discussed here can be applied to InfoWindow based implementation as well.

To render an overlay on the map using the OverlayView class, we need to implement a class which extends the OverlayView class. Here we need to flesh out 3 main functions.

- [`onAdd`](https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/reference/overlay-view#OverlayView.onAdd) Append HTML element to a container element in the rendered map canvas.
  This method is invoked once, when map attempts to render the overlay on the canvas.
- [`draw`](https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/reference/overlay-view#OverlayView.draw) Set the x, y position of the overlay content. The position _(x, y)_ of the element is translated from the _lat_, _lng_ values for the position.
  This method is called each time the content needs to be updated on the map. For example map zoom or pan.
- [`onRemove`](https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/reference/overlay-view#OverlayView.onRemove) Removes the element from the map. 
  Called when it is time to remove/hide the overlay element from the map canvas.
  
Refer to the [Custom Popups example](https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/examples/overlay-popup) and [Custom Overlay guide](https://developers-dot-devsite-v2-prod.appspot.com/maps/documentation/javascript/customoverlays) for detailed explanation about the apis and methods. 

# To kick-off, let's create a React component to render a map.
The implementation is quite straight forward, so let's dive right in. 
_See [codepen example](https://codepen.io/rihdus-1470886313/pen/oNvmVOj)._
```jsx
class Map extends React.Component {
   /** Map instance */
   map = null;
   /** DOM container where the map canvas gets rendered. */
   mapContainer = React.createRef();
   componentDidMount() {
     /** Create new google map. */
      this.map = new google.maps.Map(this.mapContainer.current, {
        zoom: this.props.zoom,
        center: this.props.center
      })
   }
   render() {
      // reference to the DOM element where the map will be rendered 
      return <div ref={this.mapContainer} 
        style={{ height: '100vh', width: '100vw'}}></div>
   }
}
ReactDOM.render(<Map />, document.getElementById('root'))
```
The important thing to note here is that the map is rendered inside the container element.
**All the markup inside the container is generated and controlled by the map library. React has no control or visibility of the DOM in the map canvas.**


# Now let's draw a popup in the map.

Here is a codepen with a react component to [draw custom HTML markup on the map](https://codepen.io/rihdus-1470886313/pen/vYBvqEE).
![Custom HTML popup on google maps. Using react.](https://thepracticaldev.s3.amazonaws.com/i/03vo8v3qx9w7zoxq00mm.png)

Fo discussion purposes, I have laid out important parts of the `CustomOverlayView` class that extends the `google.maps.OverlayView` class.
```jsx
class CustomOverlayView extends window.google.maps.OverlayView {
   constructor(props) {
      super(props);
      this.position = props.position; // lat, lng position provided by map. This is where the popup is supposed to be rendered
      this.content = props.content; // The HTML element to be rendered in the popup.

      // Create and style the popup markup.
      this.containerDiv = document.createElement("div");
      this.containerDiv.appendChild(content);
   }

   /** Called when the popup is added to the map. */
   onAdd = () => {
      this.getPanes().floatPane.appendChild(this.containerDiv);
   };

   /** Called when the popup is removed from the map. */
   onRemove = () => {
      this.content.parentElement.removeChild(this.content);
   };

   /** Called each frame when the popup needs to draw itself. */
   draw = () => {
      const divPosition = this.getProjection().fromLatLngToDivPixel(
         this.position
      );
      this.content.style.left = divPosition.x + "px";
      this.content.style.top = divPosition.y + "px";
   };
}
```

All that's left, is to create the overlay instance and attach it to the map by calling the `setMap` method in the `OverlayView` class. 
Here is the updated `componentDidMount` of the `Map` component.
```jsx
class Map extends React.Component {
	...
   componentDidMount() {
      /** Create new google map. */
      this.map = new google.maps.Map(this.mapContainer.current, {
         zoom: this.props.zoom,
         center: this.props.center
      });
      /** Create the overlay instance */
      this.popup = new CustomOverlayView({
         position: new google.maps.LatLng(
            this.props.center.lat,
            this.props.center.lng
         ),
         content: document.getElementById("content")
      });
      // Attach the overlay instance to the map. 
      // This renders the overlay on the map.
      this.popup.setMap(this.map);
   }
}
```
To create the popup element, we need to provide a DOM element to the OverlayView class. 
>This element is then appended to a container element in the map, `this.getPanes().floatPane.appendChild`. See `onAdd` method of the `CustomOverlayView` class implementation.

Now comes the weird part.
**To build the HTML content of the popup, we are getting an element from the DOM using** `document.getElementById("content")`. The react folks may find this uncomfortable.
![How in the world can you grab some element off from the DOM and insert it in some other place.](https://thepracticaldev.s3.amazonaws.com/i/2vcvs7igu6latyb7hna9.gif)
Ideally this should be a react component rendered by react.

Examples provided by google use `document.createElement('div')` , `el.classList.add('popup-container')` approach to create the HTML markup manually. 
> ### Both ways of creating the popup markup are very _non-react way of working with the DOM_.

# Let's do it the react way then.

I want to build the popup as a react component and have it rendered as part of the react [virtual DOM](https://programmingwithmosh.com/react/react-virtual-dom-explained/). 
This way, any __updates are seamlessly propagated to the popup component via props__. Just like any other react component.

The resulting jsx should look something like this...
```jsx
<Map>
  <OverlayViewContainer 
    position={{ lat: lat1, lng: lng1 }}>
	{/* my custom popup react component */}
    <CustomPopupComponent ... />
  </OverlayViewContainer>
</Map>	
```
The `OverlayViewContainer` component can encapsulate all the wiring required to integrate our custom `CustomOverlayView` class with react component tree and can render our custom react component `CustomPopupComponent` on the map at a given `position`.
This way we can have a clean interface to render react components on the map.

## Show me the code!
Here is the [sample app in the codepen](https://codepen.io/rihdus-1470886313/pen/JjPxYYV). It renders a react component as an popup overlay on the map. The popup shows the current time, which updates every second.
![React component rendered at a given position on the map.](https://thepracticaldev.s3.amazonaws.com/i/7ycydriormtp6wrchj9d.gif)


_A small update before jumping into the `OverlayViewContainer` component._
OverlayViewContainer needs the map instance where the overlay will be rendered. We can use [React.Context](https://reactjs.org/docs/context.html#reactcreatecontext) api to pass the map instance from the `<Map>` to `<OverlayViewContainer>`.
```jsx
// New context for passing down the map object from `Map` component to its children.
const MapContext = React.createContext(null);
...
class Map extends React.Component {
  ...
  render() {
    return (
      <div
        ref={...} style={...}>
        
        {/** Render the children and wrap them with MapContext.Provider component. */}
        <MapContext.Provider value={this.map}>{this.props.children}</MapContext.Provider>
      </div>
    );
  }
}
```

## Finally!! let's build the magic component.

```jsx
class OverlayViewContainer extends React.Component {
   overlay = null;
   el = null;

   render() {
      return (<MapContext.Consumer>{map => {
         if (map) {
         
            /** 
             * `OverlayView` will gracefully take this element 
             * and place it in a container on the map.
             * This element will act as the host for 
             * the child popup component to be rendered. 
             * */
            this.el = this.el || document.createElement('div');

			/** 
			 * Create the custom overlay view instance,
			 * that renders the given `content` at the given `position`
			 * */
            this.overlay = this.overlay ||
               new CustomOverlayView({
                  position: this.props.position,
                  content: this.el
               });
            this.overlay.setMap(map);

            /**
             * -----------------------------------------
             * This where React.Portal works its MAGIC.
             * The portal connects `this.props.children`
             * with `this.el`. 
             * So anything in `this.props.children` 
             * gets rendered inside `this.el` DOM element.
             * -----------------------------------------
             * */
            return ReactDOM.createPortal(this.props.children, this.el);
            
         } else {
            return null;
         }
     }}</MapContext.Consumer>);
   }
}
```
`OverlayView` will gracefully take `this.el` element and place it in a container on the map. 
`this.el` will then act as the host for the child popup component to be rendered. 
Here **React.Portal** works its magic by rendering `this.props.children` inside `this.el` _(an element that inside the google map canvas.)_.

Anything rendered as children of `OverlayViewContainer`, are still part of the react component hierarchy, despite being placed somewhere else on the DOM. 

> Its like opening a __portal__ in the react component tree which __connects to some other place on the DOM__. 
> Any component passing through this portal will be rendered at that place on the DOM.

The resulting VDOM looks like this.
```jsx
<Map>
   <MapContext.Provider>
      <OverlayViewContainer>
         <MapContext.Consumer>
            <CustomPopupComponent {...} /> 
            // The component exists in the react component heirarchy
            // but is rendered elsewhere on the actual DOM.
         </MapContext.Consumer>
      </OverlayViewContainer>
   </MapContext.Provider>
</Map>
```

# Done!

That’s a wrap! Hopefully this serves as a good example to demonstrate how React Portals can be used to render or affect DOM outside the DOM hierarchy. 
It's common to think that a UI library that renders some HTML markup on the DOM, controls everything related to that markup, and nothing else. But React Portal api allows you to extend this functionality to anywhere on the DOM.

Thanks for reading.

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/7mh014potp2yc6ia4xgu.gif)