DEV Community

Cover image for Drawing google map and pins using React.Context
Sudhir
Sudhir

Posted on • Edited on

Drawing google map and pins using React.Context

The goal

What I envisioned is a react way of rendering the map and drawing items (pins, popups etc..) on the map.

<Map>
  {// Marker component that renders a pin icon on the map }
  <Marker lat={lat1} lng={lng1} />
  <Marker lat={lat2} lng={lng2} />
</Map>

Step 1: Create Basic components to encapsulate google maps api

Google Map class renders a new map in a given DOM element and the corresponding map instance provides apis to interact with the map. Other classes like Marker , InfoWindow and Overlay allow your to draw custom UI on the map.

Map component

Here is a basic map component to render a map in a given container.

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() {
      return <div ref={this.mapContainer}
        style={{ height: '100vh', width: '100vw'}}></div>
   }
}
ReactDOM.render(<Map />, document.getElementById('root'))

Marker component

Draw the marker on a given map at a given position.
To draw a marker, we need the map object which is rendered on the DOM and pair or lat, lng values to position the marker on the map.

class Marker extends React.Component {
   componentWillUnmount() {
      this.marker.setMap(null); // Remove the marker from the map
   }
   render() { 
     const { map, lat, lng } = this.prop
     // Create new marker and render it on the map.
     this.marker =  new Marker({ 
       map: map, // the map instance
       position:  { lat, lng } // position of the marker on the map
     });
     return null; 
   }
}

For details, refer example usage for Adding a Map with a Marker provided by Google.

Step 2: Render Marker as a child component in the Map

Lets look at our target again..

ReactDOM.render(<>
    <Map>
      <Marker lat={lat1} lng={lng1} />
    </Map>
  </>, document.getElementById('root'))

The Marker component needs access to the map instance, which is created in the componentDidMount function in the Map component defined earlier.
The Map component can pass down the map instance via Render Props or using React.createContext.

Child Marker using React Context.

React Context can be used to send props from the parent Map component to child Marker component.
Let's first create a Context for the map instance using the createContext api.

The Map will provide the value of the context using the <MapContext.Provider> component.
See Context.Provider component api.

// Map context with default value of the map set to `null`.
const MapContext = React.createContext({ map: null })
...
class Map extends React.Component {
  render() {
  /**
  * Provide `map` value in map context. This value can be consumed 
  * in any child component using `<MapContext.Consumer>` component.
  */
    return <MapContext.Provider value={{map: this.map}} >
      {this.props.children}
    </MapContext.Provider>
  }
}

In the Marker component we can access the map instance using MapContext.Consumer component.
See Context.Consumer component api

class Marker extends React.Component() {
  /**
  * In the render function, we can use `<MapContext.Consumer>` component 
  * to receive the `map` received from parent `Map` component.
  */  
  render() {
    return <MapContext.Consumer>{({map}) => {
      const { lat, lng } = this.props
      // Create new marker and render it on the map.
      this.marker = this.marker || new Marker({ 
        map: this.map, // the map instance
        position:  { lat, lng }
      });
      this.marker.setPosition({ lat, lng })
      return null;
    }}</MapContext.Consumer>
  }
}

Done!

// Resulting JSX for rendering Marker on Maps.
ReactDOM.render(<>
    <Map>
      <Marker lat={lat1} lng={lng1} />
    </Map>
  </>, document.getElementById('root'))

To recap,

  • We created a Map component that renders the map canvas, and provides the instance of the corresponding map object to the children, using the Provider component in the React.Context api.
  • We use the corresponding Consumer component to retrieve the map instance in the Marker component, to draw pins in the map canvas.

Another approach is to use Render Prop technique to provide map object instance to the child Marker component.
The <MapContext.Consumer /> in Marker Component uses this render prop technique to provide access to the map instance.

A sample implementation of Child Marker using Render Prop.

class Map extends React.Component {
  render() {
    return this.props.children(this.map)
  }
}
// Render prop usage
ReactDOM.render(<>
    <Map>{map => {
      <Marker map={map} lat={lat1} lng={lng1} />
    }}</Map>
  </>, document.getElementById('root'))

React.Context is just one way to pass around data in React, there are other techniques more suitable for other use-cases. During this exercise, and others in the past, I did find some benefits of React.Context

  • resulting JSX is much cleaner
  • data provided by parent component can be accessed by any child component, at any depth, without requiring explicit prop passing.

Thanks for reading my first tech write up.
Welcome any and all feedbacks.
Cheers.

Top comments (0)