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)