Hopp til innhold

Setting up Google Maps

Sercan Leylek

2021.10.27

Let's create a comprehensive project and deliver all the secrets of Google Maps + TypeScript + React? This is the first blog of Google Maps tutorial series with a course video.

Coding with Google Maps is a fun, mysterious and challenging journey. If you do it with Typescript, both joy and pain get doubled.

When I was coding a Google Maps component for the first time, I was starving for a comprehensive tutorial on this topic. Unfortunately, the results were a bit disappointing, but like all developers I found a solution for each problem and the job was done.

On the other hand, a design system is not a single deliverable, but a collection of different services to create other applications. However, this definition should not limit us with reusable components only. Jøkul's blog posts are also a part of the design system's deliverables. Our team assessed the possibility of having a reusable Google Maps component among Jøkul's packages. After gaining enough experience and knowledge, we decided not to take this path because Google Maps itself is a very large library and answering the demands of different parties did not seem feasible. As a result of these, our team decided to support the users of Jøkul Design System with a comprehensive blog post series which will assist the consumers with technical knowledge and best practices while developing their own Google Maps component.

So, while I was working on my map component, I discovered the answers to my questions via different websites. For example:

  • How can I use TypeScript types from Google for my map project in React?
  • How should I load my Google Maps API script into my homepage? What could be the best practice?
  • Where should I use useRef to point out my Google Maps DOM element?

Some of the answers that I found were outdated, some people were not quite sure of what they were advising, etc… You know the drill.

As a result, I created a sample project on GitHub and decided to sail for a new adventure as well. For the first time, I screencast while coding. The video is 55 minutes long and you can see the whole process.

Setup and Installation

To kick off the project, we need to create a react application with typescript flag. Use the sample command below to start with:

npx create-react-app google-maps --template typescript

After removing files like logo.svg and App.test.tsx, run the command below to install Google Maps TypeScript types. This will help you access types from the google.maps namespace such as google.maps.Map, google.maps.LatLng, and so on.

npm install --save-dev @types/googlemaps

Bonus Hint: By doing so, you will also obtain google.maps.places.AutocompletePrediction type which can be used to build an address dropdown component.

Now we are ready to give a shape to our React project. I set up the file and folder structure of my src folder as blueprinted below:

├── App.css
├── App.tsx
├── Map
   ├── Map.scss
   ├── Map.tsx
   └── index.ts
├── index.css
├── index.tsx
├── react-app-env.d.ts
└── utils
   └── GoogleMapsUtils.ts

Loading the Google Maps library

While developing a Google Maps component, we need to load the script tag which will download the API library for us. At the end, our web page should show something like this:

<script
src="https://maps.googleapis.com/maps/api/js?key=<YOUR_API_KEY>&libraries=places&language=no&region=NO&v=quarterly"
async defer>
</script>

Before loading this tag into our DOM structure, we cannot run Google Maps related code pieces. Therefore, we should create a utility function under utils/GoogleMapsUtils.ts.

export const loadMapApi = () => {
    const mapsURL = `https://maps.googleapis.com/maps/api/js?key=${process.env.REACT_APP_GOOGLE_MAPS_API_KEY}&libraries=geometry,places&language=no&region=NO&v=quarterly`;
    const scripts = document.getElementsByTagName('script');
    // Go through existing script tags, and return google maps api tag when found.
    for (let i = 0; i < scripts.length; i++) {
        if (scripts[i].src.indexOf(mapsURL) === 0) {
            return scripts[i];
        }
    }

    const googleMapScript = document.createElement('script');
    googleMapScript.src = mapsURL;
    googleMapScript.async = true;
    googleMapScript.defer = true;
    window.document.body.appendChild(googleMapScript);

    return googleMapScript;
};

This function creates the aforementioned script tag and inserts it into the DOM if it is not found. loadMapApi() will be used by the page which uses our Map component. In my example, this page will be the homepage of the project (App.tsx).

Making the app ready for maps

In this application, we have two fundamental tasks for App.tsx. The first is to load the map utility, and the second is to render our Map component. To do the first, I created the React hook below:

const [scriptLoaded, setScriptLoaded] = useState(false);

This hook makes sure that Google Maps script tag is loaded successfully into the page. When scriptLoaded hook is set true we render the Map component.

useEffect(() => {
    const googleMapScript = loadMapApi();
    googleMapScript.addEventListener('load', function () {
        setScriptLoaded(true);
    });
}, []);
return (
    <div className="App">
        {scriptLoaded && (
            <Map
              mapType={google.maps.MapTypeId.ROADMAP}
              mapTypeControl={true}
            />
        )}
    </div>
);

Time to render the map

We finally reached to the heart of the application. Map.tsx is the component I mentioned earlier. Let’s create its interface (IMap) first.

interface IMap {
    mapType: google.maps.MapTypeId;
    mapTypeControl?: boolean;
}

I wanted to receive two basic props. mapType can have ROADMAP, SATELLITE, TERRAIN or HYBRID values. These are specific map types defined by Google.

And we will use mapTypeControl prop to show/hide the control menu on the left-top corner of your map. We may extend the number of props to increase the usability of our map component, but I wanted to use only two props to illustrate my example.

I also use typescript’s custom type definition feature to increase the readability of my source code.

type GoogleLatLng = google.maps.LatLng;
type GoogleMap = google.maps.Map;

Before I explain the initMap, defaultMapStart, and startMap functions, I would like to show you the render part of the Map component.

const ref = useRef<HTMLDivElement>(null);

return (
    <div className="map-container">
        <div ref={ref} className="map-container__map"></div>
    </div>
);

The inner div element is the reference point for Google Maps. The API will load my map content into this div element and I use the outer div just for styling purposes. On the other hand, the same ref value is used by initMap function where the map is triggered.

const initMap = (zoomLevel: number, address: GoogleLatLng): void => {
    if (ref.current) {
        setMap(
            new google.maps.Map(ref.current, {
                zoom: zoomLevel,
                center: address,
                mapTypeControl: mapTypeControl,
                streetViewControl: false,
                rotateControl: false,
                scaleControl: true,
                fullscreenControl: false,
                panControl: false,
                zoomControl: true,
                gestureHandling: 'cooperative',
                mapTypeId: mapType,
                draggableCursor: 'pointer',
            })
        );
    }
};

As you see above, before implementing the initMap function, I created a hook called map. This hook is used to keep the map object created by google.maps.Map(…) function. The same function takes our div element via ref.current as its first parameter and the second parameter is a json object which keeps the preferences of our map.

initMap receives two inputs: zoomLevel and address. zoomLevel is straightforward to understand, but what address does will be more clear when we take a look at defaultMapStart() function.

const defaultMapStart = (): void => {
    const defaultAddress = new google.maps.LatLng(65.166013499, 13.3698147);
    initMap(4, defaultAddress);
};

So, this function basically calls the initMap, but it also does some critical work as well. It decides where to focus and how much to focus when our map starts.

const startMap = (): void => {
    if (!map) {
        defaultMapStart();
    }
};
useEffect(startMap, [map]);

Finally, startMap function calls defaultMapStart() when the map hook is not null anymore. This check is necessary because of rendering delays and typescript will complain if you do not run this check.

Conclusion

This application may not seem so impressive in terms of functionality, but you see that we had to do a lot of things just to start our map. While shooting the video, I had a problem with the API key because I did not want to screencast it via my youtube video, but when I include it, the output is as shown below:

See you on the next tutorial/video where I will show how to add/remove markers on Google Maps. I hope you enjoyed this tutorial 🙂


(This article was previously published on Stork's Nest)