DEV Community

Gabriel Wu
Gabriel Wu

Posted on

How I wrote my own React wrapper for Google Map

A map

A few months ago, when I started the Neighborhood Map project of Udacity, I first checked the libraries available. There were quite a few choices:

However, none of them could meet my requirements (it was also possible that I did not figure out the proper way to deal with the problems). I want the components to be flexible, e.g., the Marker component does not necessarily to be placed within a Map component. This flexibility is essential when designing the layouts, and also when structuring components so as not to trigger unnecessary rerender.

What they provide (generally):

<Map>
    <Marker />
    <InfoWindow />
</Map>

What I want:

<Map />
<ComponentA>
    <Marker />
    <ComponentB>
        <InfoWindow />
    </ComponentB>
</ComponentA>

The idea came into my mind that I could write my own React wrapper for Google Map. This sounded a bit audacious because I had never written a React component library before. As the deadline of the Udacity project came closer and closer, I finally made up my mind to write my own Google Map library, with React hooks and TypeScript, and TDD.

Although I had not written a React component library, I had written a very simple Vue component library (following instructions of a blog). I had been writing TypeScript for several months, and had written a React app with context and hooks. And I had used TDD in several projects. These experiences gave me confidence.

Yet challenges did come, one after another. I had written some tests, but I had not written library mocks. I managed to mock loadjs, which I used to load Google Map scripts.

Another problem was that hooks live with functional components, and functional components do not have instances. Other Google Map libraries all use class components, and implement methods for class instances to surrogate Google Map objects' methods. But I could not do so. In the end, I chose to maintain an id-object Map to store references to all Google Map objects. It worked fluently, and could be used without using React ref (class instances rely on ref). The only price was that things like Marker, Polygon would require a unique id when using my library.

At first, I just thought about my own needs, and the API design was way too casual (you can check my original repo and time-travel to earlier versions). Later, my personal project was finished, but I knew a lot of things were still up in the air.

lucifer1004 / boycott

A map app.

Boycott

This is a Udacity project. It is statically deployed here via Now.

To run it locally

git clone https://github.com/lucifer1004/boycott
cd boycott
yarn install
yarn start

You can then visit it at localhost:3000

Features

  • Search for places using Yelp Fusion API (cors-anywhere is used to address the CORS issue)
  • Filter options: All/Open/High Rating/Low Price
  • Use of Google Map API is via @lucifer1004/react-google-map, which is a React wrapper for Google Map written by myself.

It is a simple React app, using Google Map and Yelp to implement basic place search.

Screenshot from Boycott

After submitting the project at Udacity, I went on with my library. For my personal project's needs, I only implemented MapBox, Marker, InfoWindow, HeatMap and Polygon. There were around 20 more Google Map components.

It happened several times that I had to refactor the whole library when trying to implement a new component. Luckily, I wrote unit tests for each component, and those tests helped a lot during refactors.

I spent about two weeks' spare time implementing:

  • other shapes: Circle, Polyline, Rectangle
  • layers: BicycleLayer, TrafficLayer, TransitLayer
  • search: SearchBox, StandaloneSearchBox
  • streetview: StreetView, StandaloneStreetView
  • overlays: CustomControl, GroundOverlay, KmlLayer, OverlayView
  • drawing: DrawingManager

The library started from create-react-app, I used a separate package.json in src/lib to configure the library, while the app was configured by the root level package.json. As the library grew, I felt I should set up a monorepo properly.

The week of refactoring project structure was rather tough. I read many blogs and posts on monorepos, but still could not get everything work as I hoped. I gave up once, and nearly gave up again the second time, and I made it.

With lerna and yarn workspaces, and a custom symlink,I was finally pleased with the new structure. By running yarn dev:lib and yarn dev:CRA at the same time, the example CRA app would reload each time I changed the code of the library.

I really appreciate that I decided to write my own wrapper library a month ago, otherwise I would not have learnt so much (I am going to write more posts in the series to talk about things I have learnt in detail). And I will try to further improve my library. It has not been tested in real projects. Compared to existing libraries, some functions are missing. Also there are some known issues, or limitations.

I am prepared.


Recently, I moved this project to a separate organization. Below is the repo.

logo of @googlemap-react

googlemap-react / googlemap-react

Easier Google Map Integration for React projects.

googlemap-react

npm package License: MIT codecov codebeat badge

Join the community on Spectrum

Easier Google Map Integration for React projects.

READ THE DOC

Why a new package

There has been similar packages such as tomchentw/react-google-maps google-map-react/google-map-react fullstackreact/google-maps-react so why bother writing a new library?

The aim is to make an easier-to-use Google Map library for React users, empowered by React's latest features (React >= 16.8.0 is required) and TypeScript.

What is different

  • Component position is free (generally)
  • Direct access to Google Map objects
  • More uniform API
  • Type safe

Example usage

Prerequisites

  • npm or yarn
yarn add @googlemap-react/core
# Or you can use
npm install --save @googlemap-react/core
  • a valid Google Map API key (to replace the place holder in the code snippet below)
import {
  GoogleMapProvider
  HeatMap,
  InfoWindow,
  MapBox,
  Marker,
  Polygon,
} from '@lucifer1004/react-google-map'

// In your component
return (
  <GoogleMapProvider>
    <MapBox
      apiKey="YOUR_GOOGLE_MAP_API_KEY

Any advice or suggestions are welcome! If you want to use my library and run into any problem, just ask me!

If you want to join, that would be great!

Top comments (12)

Collapse
 
rainbow_shout profile image
Rainbow Shout

Hi Gabriel,

Do you have a recommended implemenatation of MarkerClusterer and / or Spiderfier?

Currently i'm looking at adding my marker clusterer script generator inside my MapContainer componentDidMount, but i'm not sure how to reference the 'map' item.

Many thanks,

James

Collapse
 
lucifer1004 profile image
Gabriel Wu

If you are using my wrapper, you can get the reference to map via useContext.

import React, {useContext} from 'react'
import {GoogleMapContext} from '@googlemap-react/core'

const MyComponent = () => {
  const [state, dispatch] = useContext(GoogleMapContext)

  // You can now access the map object and other objects via `state`.
  // Remember to check existence, since they might be undefined.
  state.map && state.map.setZoom(12)

  return null
}
Collapse
 
lucifer1004 profile image
Gabriel Wu

Since you mentioned componentDidMount, you are using class components instead of functional components, then you should just wrap your component with GoogleMapConsumer.

Collapse
 
rainbow_shout profile image
Rainbow Shout

If I want to use the context outside of the render method am I able to use, for example:

import {GoogleMapContext} from '@googlemap-react/core'

class MapContainer extends React.Component {

static contextType = GoogleMapContext;

//etc

and then access the map by this.context.state.map?

Thread Thread
 
rainbow_shout profile image
Rainbow Shout

Hm, I tried the way above and also refactored to a functional component and just used:

import React, {useContext} from "react";
import {
GoogleMapProvider,
MapBox,
Marker,
InfoWindow,
GoogleMapContext
} from "@googlemap-react/core";

const MapContainer = props => {

const map = useContext(GoogleMapContext);
console.log(map);

//etc

and I'm just seeing {state: undefined, dispatch: undefined} as my console output for map, any idea why :/

Collapse
 
rainbow_shout profile image
Rainbow Shout

Great little wrapper for Google Maps - only problem I've run into is activating the InfoWindow on Marker click - am I missing something obvious here?

Thanks,

James

Collapse
 
lucifer1004 profile image
Gabriel Wu

Hi James, here is an example of InteractiveMarker.

Collapse
 
rainbow_shout profile image
Rainbow Shout

Amazing, thanks Gabriel :)

I'd actually made something slightly different - an iterator that creates the InfoWindows and their anchor positions, and then an onClick for each marker that passes out the marker ID as an action to a Redux store, changing the relevant infoWindow's visibility.

Really enjoying working with your library!

Collapse
 
rainbow_shout profile image
Rainbow Shout

Actually, there was one other thing - is there a list of event handlers for each component?

For example, I can set an onClick handler on Mapbox and that deals with any click events on the map itself - are there handlers for onLoad, onZoomChange, etc?

Many thanks, James

Thread Thread
 
lucifer1004 profile image
Gabriel Wu

See PROPS & METHODS section for each component in the documentation.

Thread Thread
 
rainbow_shout profile image
Rainbow Shout

Thanks :)

Collapse
 
alexpaper profile image
alexpaper

Amazing!Thanks! Fast and simple!!!!