Back to all posts

Using Google Maps in React

Special thanks to Nicolai Gonçalves @pentiaDK for the inspiration for this one.

When using react and you want some big features written by somebody else, you usually want to install a relevant NPM package that comes with all the functions you need. In case of services like Google Maps, there is not a complete updated package from Google to install. It might be tempting to install a package that works like a wrapper around Google Maps functions, but this might only be limiting to you as a developer. You can't be sure that the package is continuously updated to support future features you might want.

In this blog post I'm going to show you a way to work with the Google Maps API in larger React applications without a NPM package, so you are free to implement future features without having to rely on a third party package.

Say you have multiple map components on a page, or in a case of React, you might even have multiple map components on multiple pages. These should all share the same source of the Google Maps API.

Creating a custom API handler

This is an example file structure for possible map components in React. At the top I created the shared API-handler. This is going to be imported into the components that will be using the API.

filestructure.jpg

The code for this is the following, I'm gonna explain whats going on here.

export default class GoogleMapsApi {
constructor() {
    this.apiKey = 'YOURAPIKEY';
}
load() {
    if (!this.promise) {
        this.promise = new Promise((resolve) => {
            this.resolve = resolve;
            if (typeof window.google === 'undefined') {
                const s = document.createElement('script');
                s.src = `//maps.googleapis.com/maps/api/js?key=${this.apiKey}`;
                s.async = true;
                document.body.appendChild(s);

                s.addEventListener('load', () => {
                    if(this.resolve) {
                        this.resolve();
                    }
                });

            } else {
                this.resolve();
            }
        });
    } 
    return this.promise;
}
}

Here we can gather all the configuration we need for the Google Maps API, like the API key. When the load function is triggered, it will append the Google Maps script tag onto the page and let the components know when it's ready to use.

This will be causing a race condition, depending on what component triggered the Google Maps API. We can't (and shouldn't) keep track on what component was responsible for the new script tag requesting the API. We should only have to know when the API is ready to use.

Promises to the rescue

The load function returns a promise that will be resolved if one of two conditions is met.

  1. The 'load' event listener is triggered, this will happen when the first component triggers the introduction of the new script tag.
  2. window.google is already defined.

This makes sure that we load the script only once and the promise returned by the class will be resolved either way.

Using the script

We can now import and start using the script in the component, in this case, the map-view component.

filestructure2.jpg

import  { GoogleMapsApi } from './';

In componentDidMount we can use the promise to let us know when it's safe to start using the map.

componentDidMount() {
    googleMapsApi.load().then(() => this.initMap());
}

initMap() {
    this.map = new window.google.maps.Map(document.getElementById('map-target'), {});
}

You can now start implementing all the functions for the specific component you're working on.

Bonus!

Don't forget that you can customize your map styling. If you don't know where to start, snazzymaps.com is a great resource! You can customize your map and download the JSON-configuration for it quickly.

The following code example is how you can implement your map style.

import React, { Component } from 'react';
import  { GoogleMapsApi } from './';

const styleConfig= [
    {
        featureType: 'poi.business',
        stylers: [{ visibility: 'off' }]
    },
    {
        featureType: 'transit',
        elementType: 'labels.icon',
        stylers: [{ visibility: 'off' }]
    },
    {
        "elementType": "geometry",
        "stylers": [
            {
                "color": "#ebe3cd"
            }
        ]
    }
];

class MapView extends Component {
    constructor(props) {
        super(props);
        this.map = undefined;
    }
    componentDidMount() {
        googleMapsApi.load().then(() => this.initMap());
    }
    initMap() {
            const mapStyle = new window.google.maps.StyledMapType(styleConfig);
            this.map = new window.google.maps.Map(document.getElementById('map-target'), {});
            this.map.mapTypes.set('styled_map', mapStyle);
            this.map.setMapTypeId('styled_map');
    }

    render() {
        return (
            <div id='target-map'></div>
        );
    }
}
export default MapView;

Happy coding!

This article is my 2nd oldest. It is 683 words long, and it’s got 2 comments for now.