Lazy load a Google Map
Tags: webdev, javascript, typescript, react
Published: 2024-04-08 01:57:49

Google Maps is a powerful tool to add interactive maps to your website. However, embedding a Google Map can negatively affect your site's performance, especially on mobile devices. The map may take a while to load, slowing down your page and frustrating users.

Luckily, we can use lazy loading to defer loading the Google Map until it's actually needed. By using the IntersectionObserver API, we can detect when the map container enters the viewport and only load the map then. This can significantly improve your site's initial load time and performance.

In this post, we'll show you how to lazy load a Google Map in a React component using IntersectionObserver.

## Creating the map container

To start, we need to create a container for our Google Map in our React component. By giving the container a `ref`, we can easily reference it later with IntersectionObserver.

```tsx
const mapRef = React.useRef<HTMLDivElement>(null);

// Render
return (
    {/* Other content */}
    <div ref={mapRef} />
);
```

## Setting up the IntersectionObserver

Now, we're going to set up an IntersectionObserver to keep an eye on the map container and load the map only when it enters the viewport.

To create an IntersectionObserver, we need to create an instance of the `IntersectionObserver` class. The constructor takes two arguments: a callback function to execute when the observed element intersects with the viewport, and an options object that specifies additional parameters for the observer.

We also define an options object that sets a visibility `threshold` of 0. This means that our callback function will be triggered as soon as even a single pixel of our map container is visible within the viewport.

In our case, we want to observe the map container and load the map only when it enters the viewport. To do this, we'll pass a callback function to the constructor that will iterate through each entry in the `entries` parameter and check if it's intersecting with the viewport using `entry.isIntersecting`. If it is, we'll load our Google Map and stop observing further changes by calling `observer.unobserve(mapEle)`. This way, we're only loading the map once, and only when it's necessary.

```tsx
React.useEffect(() => {
    const mapEle = mapRef.current;
    if (!mapEle) {
        return;
    }

    const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
            if (entry.isIntersecting) {
                // Load the map ...
                observer.unobserve(mapEle);
            }
        });
    }, {
        threshold: 0,
    });
}, []);
```

Next, we call its `observe()` method with our map container element as its argument. This starts watching the element's visibility. Finally, we return a cleanup function from the `useEffect()` hook. This function unobserves our map container element when it's removed from the DOM or when our component is unmounted.

```tsx
React.useEffect(() => {
    ...
    observer.observe(mapEle);

    return (): void => {
        observer.unobserve(mapEle);
    };
}, []);
```

## Loading Google Maps with a specific callback

Once the IntersectionObserver callback is triggered, we can load the Google Maps. To achieve that, we'll create a `script` tag that loads the Google Maps JavaScript API. The `API_KEY` provided by Google will be passed to the `src` attribute of the script. Once the API is loaded, it will call the `initMap` function specified within the script.

```js
const API_KEY = '...';

const script = document.createElement('script');
script.src = `https://maps.googleapis.com/maps/api/js?key=${API_KEY}&callback=initMap`;
script.async = true;
document.body.appendChild(script);

window.initMap = initMap;
```

The `initMap` function is responsible for creating a new instance of Google Map. It specifies the map container element and some initial options like center coordinates and zoom level.

```js
const initMap = () => {
    const mapEle = mapRef.current;
    if (mapEle) {
        new window.google.maps.Map(mapEle, {
            center: new window.google.maps.LatLng(40.73061, -73.935242), // New York city
            zoom: 10,
        });
    }
};
```

Take a look at the demo below. You can scroll down to see how the map loads dynamically:

{% stackblitz https://stackblitz.com/edit/lazy-load-a-google-map-1?file=src%2FApp.tsx&view=preview&embed=1 %}

If you'd rather not rely on the `callback` parameter, there's another way to ensure that the Google Map API is fully loaded before we try to use it. We can listen to its `load` event. This is crucial because attempting to create a new instance of the Google Map before the API is fully loaded will result in an error.

Here's how to do it:

```js
const script = document.createElement('script');
script.addEventListener('load', () => {
    new window.google.maps.Map(mapEle, {
        center: new window.google.maps.LatLng(40.73061, -73.935242), // New York city
        zoom: 10,
    });
});
```

Take a look at the demo below:

{% stackblitz https://stackblitz.com/edit/lazy-load-a-google-map-2?file=src%2FApp.tsx&view=preview&embed=1 %}

## Conclusion

Using IntersectionObserver to lazy load our Google Map can greatly improve our website's performance. The map will only load when the user scrolls it into view, reducing the initial load time of the page.

This technique isn't just limited to Google Maps. It can be applied to any resource that's expensive to load. Lazy loading is a powerful tool that can optimize your site's performance and provide a better user experience.

---

If you want more helpful content like this, feel free to follow me:

-   [DEV](https://dev.to/phuocng)
-   [GitHub](https://github.com/phuocng)
-   [Website](https://phuoc.ng)